Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions packtools/sps/models/supplementary_material.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ def __getattr__(self, name):
if self.graphic is not None and hasattr(self.graphic, name):
return getattr(self.graphic, name)



raise AttributeError(f"SupplementaryMaterial has no attribute {name}")

@property
Expand All @@ -52,17 +50,28 @@ def xml(self):
else:
return "<supplementary-material>"

@property
def has_content(self):
return self.media is not None or self.graphic is not None

@property
def data(self):
base_data = super().data.copy()
suppl_label = base_data.get("label")
suppl_caption = base_data.get("caption")
base_data.update(self.media.data if self.media else {})
base_data.update(self.graphic.data if self.graphic else {})
base_data.update(
{
"id": self.id,
"parent_suppl_mat": self.parent_tag,
"sec_type": self.sec_type,
"visual_elem": "media" if self.media else "graphic",
# FIX Problema 3: quando has_content=False, visual_elem era "graphic"
# em vez de None, mascarando o diagnóstico de validate_content.
"visual_elem": "media" if self.media else ("graphic" if self.graphic else None),
"suppl_label": suppl_label,
"suppl_caption": suppl_caption,
"has_content": self.has_content,
}
)

Expand Down
143 changes: 127 additions & 16 deletions packtools/sps/validation/supplementary_material.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from lxml import etree
from langdetect import detect
from packtools.sps.models.supplementary_material import XmlSupplementaryMaterials
from packtools.sps.models.media import XmlMedias
from packtools.sps.models.graphic import Graphic, XmlGraphic
from packtools.sps.validation.graphic import GraphicValidation
from packtools.sps.validation.media import MediaValidation
from packtools.sps.validation.utils import build_response
Expand All @@ -14,7 +11,8 @@ def __init__(self, data, params):
Inicializa a validação de um material suplementar.

Args:
supp (dict): Dados do material suplementar extraídos do modelo
data (dict): Dados do material suplementar extraídos do modelo
params (dict): Parâmetros de configuração de validação
"""
self.data = data
self.params = params
Expand All @@ -25,9 +23,31 @@ def validate(self):
"""
yield from MediaValidation(self.data, self.params).validate()
yield from GraphicValidation(self.data, self.params).validate()
yield self.validate_id()
yield self.validate_sec_type()
yield self.validate_label()
yield self.validate_not_in_app_group()
yield self.validate_content()

def validate_id(self):
"""
Verifica a presença obrigatória de @id em <supplementary-material>.
"""
suppl_id = self.data.get("id")
valid = bool(suppl_id)
return build_response(
title="supplementary-material @id",
parent=self.data,
item="supplementary-material",
sub_item="@id",
is_valid=valid,
validation_type="exist",
expected="@id in <supplementary-material>",
obtained=suppl_id,
advice='Add @id to <supplementary-material>: <supplementary-material id="...">. Consult SPS documentation for more detail.',
error_level=self.params["id_error_level"],
data=self.data,
)

def validate_sec_type(self):
"""
Expand All @@ -36,6 +56,23 @@ def validate_sec_type(self):
if self.data.get("parent_suppl_mat") == "sec":
sec_type = self.data.get("sec_type")
valid = sec_type == "supplementary-material"
# FIX Problema 2: obtained usava self.data.get("parent_tag") — chave inexistente
# no dict de dados (a chave correta é parent_suppl_mat) — sempre retornava None.
# obtained deve refletir o valor encontrado para @sec-type, não a tag pai.
# FIX Problema 2 (advice): bifurca a mensagem conforme o caso:
# - @sec-type ausente → instruir a *adicionar* o atributo (não "substituir")
# - @sec-type com valor errado → instruir a *substituir* pelo valor correto
# Ambas as mensagens permanecem em inglês, sem placeholder em português.
if sec_type is None:
advice = (
'Add @sec-type="supplementary-material" to the enclosing <sec>: '
'<sec sec-type="supplementary-material">.'
)
else:
advice = (
f'In <sec sec-type="{sec_type}"><supplementary-material> '
f'replace "{sec_type}" with "supplementary-material".'
)
return build_response(
title="@sec-type",
parent=self.data,
Expand All @@ -44,17 +81,19 @@ def validate_sec_type(self):
is_valid=valid,
validation_type="match",
expected="<sec sec-type='supplementary-material'>",
obtained=self.data.get("parent_tag"),
advice=f'In <sec sec-type="{sec_type}"><supplementary-material> replace "{sec_type}" with "supplementary-material".',
obtained=sec_type,
advice=advice,
error_level=self.params["sec_type_error_level"],
data=self.data,
)

def validate_label(self):
"""
Verifica a presença obrigatória de <label>
Verifica a presença obrigatória de <label> em <supplementary-material>.
Uses suppl_label which preserves the supplementary-material's own label
even when media/graphic data is merged.
"""
label = self.data.get("label")
label = self.data.get("suppl_label") or self.data.get("label")
valid = bool(label)
return build_response(
title="label",
Expand Down Expand Up @@ -89,19 +128,37 @@ def validate_not_in_app_group(self):
data=self.data,
)

def validate_content(self):
"""
Verifica a presença de <graphic> ou <media> dentro de <supplementary-material>.
"""
has_content = self.data.get("has_content", False)
valid = bool(has_content)
return build_response(
title="supplementary-material content",
parent=self.data,
item="supplementary-material",
sub_item="graphic or media",
is_valid=valid,
validation_type="exist",
expected="<graphic> or <media> in <supplementary-material>",
obtained=self.data.get("visual_elem") if has_content else None,
advice="Add <graphic> (for figures) or <media> (for other types) inside <supplementary-material>.",
error_level=self.params["content_error_level"],
data=self.data,
)


class XmlSupplementaryMaterialValidation:
def __init__(self, xml_tree, params):
self.article_supp = list(XmlSupplementaryMaterials(xml_tree).items)
self.xml_tree = xml_tree
self.params = params


def validate_prohibited_inline(self):
"""
Ensures that <inline-supplementary-material> is not used.
"""

nodes = self.xml_tree.xpath(".//inline-supplementary-material")
obtained = etree.tostring(nodes[0]) if nodes else "None"
valid = not bool(nodes)
Expand All @@ -124,8 +181,12 @@ def validate_position(self):
"""
Verifies if the supplementary materials section is in the last position of <body> or inside <back>.
"""
sections = self.xml_tree.xpath('.//sec[@sec-type="supplementary-material"]')
if not sections:
# FIX Problema 4: a variável "sections" era sobrescrita dentro dos blocos
# if article_body / if article_back, causando shadowing silencioso.
# Renomeada para suppl_sections (resultado do xpath inicial) e body_sections /
# back_sections (resultados dos findall internos) para eliminar o risco.
suppl_sections = self.xml_tree.xpath('.//sec[@sec-type="supplementary-material"]')
if not suppl_sections:
return

article_body = self.xml_tree.find("body")
Expand All @@ -135,14 +196,14 @@ def validate_position(self):
is_in_back = False

if article_body is not None:
sections = article_body.findall("sec")
if sections and sections[-1].get("sec-type") == "supplementary-material":
body_sections = article_body.findall("sec")
if body_sections and body_sections[-1].get("sec-type") == "supplementary-material":
is_last_in_body = True

if article_back is not None:
sections = article_back.findall("sec")
back_sections = article_back.findall("sec")
is_in_back = any(
sec.get("sec-type") == "supplementary-material" for sec in sections
sec.get("sec-type") == "supplementary-material" for sec in back_sections
)

valid = is_last_in_body or is_in_back
Expand All @@ -168,6 +229,54 @@ def validate_position(self):
data={},
)

def validate_sec_title(self):
"""
Validates that <sec sec-type="supplementary-material"> contains a <title> element.
"""
sections = self.xml_tree.xpath('.//sec[@sec-type="supplementary-material"]')
for sec in sections:
title_elem = sec.find("title")
title_text = title_elem.text if title_elem is not None else None
valid = bool(title_text)
yield build_response(
title="sec supplementary-material title",
parent={},
item="sec",
sub_item="title",
is_valid=valid,
validation_type="exist",
expected='<title> in <sec sec-type="supplementary-material">',
obtained=title_text,
advice='Add <title> to <sec sec-type="supplementary-material">: <sec sec-type="supplementary-material"><title>...</title></sec>.',
error_level=self.params["sec_title_error_level"],
data={},
)

def validate_id_uniqueness(self):
"""
Validates that each <supplementary-material> has a unique @id.
"""
seen_ids = {}
for supp in self.article_supp:
suppl_id = supp.get("id")
if not suppl_id:
continue
if suppl_id in seen_ids:
yield build_response(
title="supplementary-material @id uniqueness",
parent=supp,
item="supplementary-material",
sub_item="@id",
is_valid=False,
validation_type="unique",
expected="Unique @id for each <supplementary-material>",
obtained=f'Duplicate @id="{suppl_id}"',
advice=f'Replace duplicate @id="{suppl_id}" with a unique value in <supplementary-material>.',
error_level=self.params["id_uniqueness_error_level"],
data=supp,
)
seen_ids[suppl_id] = True

def validate(self):
for supp in self.article_supp:
yield from SupplementaryMaterialValidation(
Expand All @@ -176,3 +285,5 @@ def validate(self):

yield self.validate_prohibited_inline()
yield self.validate_position()
yield from self.validate_sec_title()
yield from self.validate_id_uniqueness()
2 changes: 1 addition & 1 deletion packtools/sps/validation/xml_validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ def validate_app_group(xmltree, params):


def validate_supplementary_materials(xmltree, params):
# TODO
rules = {}
rules.update(params["visual_resource_base_rules"])
rules.update(params["supplementary_materials_rules"])
validator = XmlSupplementaryMaterialValidation(xmltree, rules)
yield from validator.validate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"position_error_level": "CRITICAL",
"label_error_level": "CRITICAL",
"app_group_error_level": "CRITICAL",
"inline_error_level": "CRITICAL",
"inline_error_level": "ERROR",
"id_error_level": "CRITICAL",
"sec_title_error_level": "CRITICAL",
"content_error_level": "WARNING",
"id_uniqueness_error_level": "ERROR",
"mime_correspondence": {
"pdf": "application",
"zip": "application",
Expand Down
Loading