From 3a042a19d3c255b05bd2b286c05a50f378153db2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:11:26 +0000 Subject: [PATCH 1/6] Initial plan From ae4535ff5073ed95cf32c07ffd27ca30540ef58d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:16:36 +0000 Subject: [PATCH 2/6] Implement 8 fig validation rules according to SPS 1.10 Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- packtools/sps/models/fig.py | 31 ++- packtools/sps/validation/fig.py | 252 ++++++++++++++---- packtools/sps/validation_rules/fig_rules.json | 21 +- 3 files changed, 243 insertions(+), 61 deletions(-) diff --git a/packtools/sps/models/fig.py b/packtools/sps/models/fig.py index 07009b9cc..6516f6246 100644 --- a/packtools/sps/models/fig.py +++ b/packtools/sps/models/fig.py @@ -62,12 +62,12 @@ def file_extension(self): @property def graphic_alt_text(self): """ - Extracts alt-text from graphic within alternatives. + Extracts alt-text from graphic (anywhere in fig, not just in alternatives). Returns: str or None: The text content of if present, None otherwise. """ - graphic = self.element.find(".//alternatives/graphic") + graphic = self.element.find(".//graphic") if graphic is not None: alt_text_elem = graphic.find("alt-text") if alt_text_elem is not None: @@ -77,18 +77,39 @@ def graphic_alt_text(self): @property def graphic_long_desc(self): """ - Extracts long-desc from graphic within alternatives. + Extracts long-desc from graphic (anywhere in fig, not just in alternatives). Returns: str or None: The text content of if present, None otherwise. """ - graphic = self.element.find(".//alternatives/graphic") + graphic = self.element.find(".//graphic") if graphic is not None: long_desc_elem = graphic.find("long-desc") if long_desc_elem is not None: return long_desc_elem.text return None + @property + def xml_lang(self): + """ + Extracts xml:lang attribute from fig element. + + Returns: + str or None: The xml:lang attribute value if present, None otherwise. + """ + return self.element.get("{http://www.w3.org/XML/1998/namespace}lang") + + @property + def parent_name(self): + """ + Gets the tag name of the parent element. + + Returns: + str or None: The parent tag name if present, None otherwise. + """ + parent = self.element.getparent() + return parent.tag if parent is not None else None + @property def data(self): return { @@ -103,6 +124,8 @@ def data(self): "file_extension": self.file_extension, "graphic_alt_text": self.graphic_alt_text, "graphic_long_desc": self.graphic_long_desc, + "xml_lang": self.xml_lang, + "parent_name": self.parent_name, } diff --git a/packtools/sps/validation/fig.py b/packtools/sps/validation/fig.py index 2c7f25f29..546d17479 100644 --- a/packtools/sps/validation/fig.py +++ b/packtools/sps/validation/fig.py @@ -6,7 +6,7 @@ class ArticleFigValidation: def __init__(self, xml_tree, rules): self.xml_tree = xml_tree self.rules = rules - self.article_types_requires = rules["article_types_requires"] + self.article_types_requires = rules.get("article_types_requires", []) self.article_type = xml_tree.find(".").get("article-type") self.required = self.article_type in self.article_types_requires self.elements = list(ArticleFigs(xml_tree).get_all_figs) @@ -15,13 +15,12 @@ def validate(self): if self.elements: for element in self.elements: yield from FigValidation(element, self.rules).validate() - - else: + elif self.required: yield format_response( title="fig presence", parent="article", parent_id=None, - parent_article_type=self.xml_tree.get("article-type"), + parent_article_type=self.article_type, parent_lang=self.xml_tree.get( "{http://www.w3.org/XML/1998/namespace}lang" ), @@ -31,9 +30,9 @@ def validate(self): is_valid=False, expected="", obtained=None, - advice=f'({self.article_type}) No found in XML', + advice=f'article-type={self.article_type} requires . Found 0. Identify the fig or check if article-type is correct', data=None, - error_level=self.rules["absent_error_level"], + error_level=self.rules.get("absent_error_level", "WARNING"), ) @@ -45,80 +44,237 @@ def __init__(self, data, rules): def get_default_params(self): return { - "alternatives_error_level": "CRITICAL", - "error_level": "WARNING", - "required_error_level": "CRITICAL", "absent_error_level": "WARNING", "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] + "graphic_error_level": "CRITICAL", + "xlink_href_error_level": "CRITICAL", + "file_extension_error_level": "ERROR", + "fig_type_error_level": "ERROR", + "xml_lang_in_fig_group_error_level": "ERROR", + "accessibility_error_level": "WARNING", + "alt_text_length_error_level": "WARNING", + "allowed_file_extensions": ["jpg", "jpeg", "png", "tif", "tiff"], + "allowed_fig_types": ["graphic", "chart", "diagram", "drawing", "illustration", "map"], + "alt_text_max_length": 120, + "article_types_requires": [] } def validate(self): - yield from self._validate_item("id") - yield from self._validate_item("label") - yield from self._validate_item("caption") - yield from self.validate_content() - yield from self.validate_file_extension() - - def _validate_item(self, name): - obtained = self.data.get(name) + # P0 - Critical: Rule 1 - Validate @id presence + yield self.validate_id() + + # P0 - Critical: Rule 2 - Validate presence + yield self.validate_graphic() + + # P0 - Critical: Rule 3 - Validate @xlink:href in + yield self.validate_xlink_href() + + # P0 - ERROR: Rule 4 - Validate file extension + yield self.validate_file_extension() + + # P0 - ERROR: Rule 5 - Validate @fig-type values + if self.data.get("type"): # Only validate if fig-type is present + yield self.validate_fig_type() + + # P1 - ERROR: Rule 6 - Validate @xml:lang in fig-group + if self.data.get("parent_name") == "fig-group": + yield self.validate_xml_lang_in_fig_group() + + # P1 - WARNING: Rule 7 - Validate accessibility (alt-text or long-desc) + yield self.validate_accessibility() + + # P1 - WARNING: Rule 8 - Validate alt-text length + if self.data.get("graphic_alt_text"): + yield self.validate_alt_text_length() + + def validate_id(self): + """Rule 1: Validate presence of @id (CRITICAL)""" + obtained = self.data.get("id") is_valid = bool(obtained) - key_error_level = f"{name}_error_level" - advice = f'Mark each figure {name} inside using <{name}>. Consult SPS documentation for more detail.' - yield build_response( - title=name, + return build_response( + title="@id", parent=self.data, item="fig", - sub_item=name, + sub_item="@id", validation_type="exist", is_valid=is_valid, - expected=name, + expected="@id attribute", obtained=obtained, - advice=advice, + advice='Add @id attribute to . Example: . The @id attribute is mandatory.', + data=self.data, + error_level=self.rules["id_error_level"], + ) + + def validate_graphic(self): + """Rule 2: Validate presence of (CRITICAL)""" + obtained = self.data.get("graphic") + is_valid = bool(obtained) + return build_response( + title="", + parent=self.data, + item="fig", + sub_item="graphic", + validation_type="exist", + is_valid=is_valid, + expected=" element", + obtained=obtained, + advice='Add element inside . Example: . Every must contain at least one element.', data=self.data, - error_level=self.rules[key_error_level], + error_level=self.rules["graphic_error_level"], ) - def validate_content(self): - is_valid = bool(self.data.get("graphic") or self.data.get("alternatives")) - name = "graphic or alternatives" - yield build_response( - title=name, + def validate_xlink_href(self): + """Rule 3: Validate @xlink:href in (CRITICAL)""" + graphic = self.data.get("graphic") + is_valid = bool(graphic) + return build_response( + title="@xlink:href", parent=self.data, item="fig", - sub_item=name, + sub_item="@xlink:href", validation_type="exist", is_valid=is_valid, - expected=name, - obtained=None, - advice='Ensure that the figure contains either or inside . Consult SPS documentation for more detail.', + expected="@xlink:href attribute in ", + obtained=graphic, + advice='Add @xlink:href attribute to . Example: . The @xlink:href attribute is mandatory in .', data=self.data, - error_level=self.rules["content_error_level"], + error_level=self.rules["xlink_href_error_level"], ) def validate_file_extension(self): + """Rule 4: Validate file extension (ERROR)""" file_extension = self.data.get("file_extension") - file_path = self.data.get('graphic') - allowed_file_extensions = self.rules["allowed file extensions"] - is_valid = file_extension in allowed_file_extensions - if file_extension: - advice = f'In replace {file_extension} with one of {allowed_file_extensions}' + graphic_href = self.data.get("graphic") + allowed_extensions = self.rules["allowed_file_extensions"] + alternative_elements = self.data.get("alternative_elements", []) + + # Check if file is SVG and if it's inside alternatives + is_svg = file_extension == "svg" + is_in_alternatives = len(alternative_elements) > 0 + + # SVG is only allowed inside alternatives + if is_svg: + is_valid = is_in_alternatives + if not is_valid: + advice = f'SVG files are only allowed inside . Either use a different format ({", ".join(allowed_extensions)}) or wrap the graphic in .' + else: + advice = None else: - advice = f'In specify a valid file extension from: {allowed_file_extensions}' - yield build_response( + is_valid = file_extension in allowed_extensions if file_extension else False + if file_extension and not is_valid: + advice = f'File extension "{file_extension}" is not allowed. Use one of: {", ".join(allowed_extensions)}. If using SVG, it must be inside .' + elif not file_extension: + advice = f'File "{graphic_href}" must have a valid extension. Allowed: {", ".join(allowed_extensions)}. SVG is only allowed inside .' + else: + advice = None + + return build_response( title="file extension", parent=self.data, item="fig", sub_item="file extension", validation_type="value in list", is_valid=is_valid, - expected=allowed_file_extensions, + expected=f'{", ".join(allowed_extensions)} (.svg only in )', obtained=file_extension, advice=advice, data=self.data, error_level=self.rules["file_extension_error_level"], ) + + def validate_fig_type(self): + """Rule 5: Validate @fig-type values (ERROR)""" + fig_type = self.data.get("type") + allowed_types = self.rules["allowed_fig_types"] + is_valid = fig_type in allowed_types if fig_type else True # If not present, it's valid + + return build_response( + title="@fig-type", + parent=self.data, + item="fig", + sub_item="@fig-type", + validation_type="value in list", + is_valid=is_valid, + expected=f'one of {allowed_types}', + obtained=fig_type, + advice=f'Invalid @fig-type value "{fig_type}". Use one of: {", ".join(allowed_types)}.', + data=self.data, + error_level=self.rules["fig_type_error_level"], + ) + + def validate_xml_lang_in_fig_group(self): + """Rule 6: Validate @xml:lang in inside (ERROR)""" + xml_lang = self.data.get("xml_lang") + is_valid = bool(xml_lang) + + return build_response( + title="@xml:lang in fig-group", + parent=self.data, + item="fig", + sub_item="@xml:lang", + validation_type="exist", + is_valid=is_valid, + expected="@xml:lang attribute", + obtained=xml_lang, + advice='When is inside , the @xml:lang attribute is mandatory. Add xml:lang attribute to . Example: .', + data=self.data, + error_level=self.rules["xml_lang_in_fig_group_error_level"], + ) + + def validate_accessibility(self): + """Rule 7: Validate presence of alt-text or long-desc (WARNING)""" + alt_text = self.data.get("graphic_alt_text") + long_desc = self.data.get("graphic_long_desc") + has_accessibility = bool(alt_text or long_desc) + + return build_response( + title="accessibility", + parent=self.data, + item="fig", + sub_item="alt-text or long-desc", + validation_type="exist", + is_valid=has_accessibility, + expected=" or ", + obtained="present" if has_accessibility else None, + advice='For accessibility, add or inside . Example: Brief description.', + data=self.data, + error_level=self.rules["accessibility_error_level"], + ) + + def validate_alt_text_length(self): + """Rule 8: Validate alt-text character limit (WARNING)""" + alt_text = self.data.get("graphic_alt_text") + max_length = self.rules["alt_text_max_length"] + + if not alt_text: + return build_response( + title="alt-text length", + parent=self.data, + item="fig", + sub_item="alt-text length", + validation_type="format", + is_valid=True, + expected=f"≤ {max_length} characters", + obtained=None, + advice=None, + data=self.data, + error_level=self.rules["alt_text_length_error_level"], + ) + + current_length = len(alt_text) + is_valid = current_length <= max_length + + return build_response( + title="alt-text length", + parent=self.data, + item="fig", + sub_item="alt-text length", + validation_type="format", + is_valid=is_valid, + expected=f"≤ {max_length} characters", + obtained=f"{current_length} characters", + advice=f'The content has {current_length} characters, exceeding the recommended maximum of {max_length}. Please shorten the description.', + data=self.data, + error_level=self.rules["alt_text_length_error_level"], + ) + diff --git a/packtools/sps/validation_rules/fig_rules.json b/packtools/sps/validation_rules/fig_rules.json index b07f32b30..36a405f3a 100644 --- a/packtools/sps/validation_rules/fig_rules.json +++ b/packtools/sps/validation_rules/fig_rules.json @@ -1,14 +1,17 @@ { "fig_rules": { - "alternatives_error_level": "CRITICAL", - "error_level": "WARNING", - "required_error_level": "CRITICAL", "absent_error_level": "WARNING", "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] + "graphic_error_level": "CRITICAL", + "xlink_href_error_level": "CRITICAL", + "file_extension_error_level": "ERROR", + "fig_type_error_level": "ERROR", + "xml_lang_in_fig_group_error_level": "ERROR", + "accessibility_error_level": "WARNING", + "alt_text_length_error_level": "WARNING", + "allowed_file_extensions": ["jpg", "jpeg", "png", "tif", "tiff"], + "allowed_fig_types": ["graphic", "chart", "diagram", "drawing", "illustration", "map"], + "alt_text_max_length": 120, + "article_types_requires": ["research-article"] } -} \ No newline at end of file +} From 4c30632c25415692d3552068e14f1a20dfa5dff1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:18:05 +0000 Subject: [PATCH 3/6] Add comprehensive tests for all 8 fig validation rules Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- tests/sps/validation/test_fig.py | 763 +++++++++++++-------------- tests/sps/validation/test_fig_old.py | 447 ++++++++++++++++ 2 files changed, 823 insertions(+), 387 deletions(-) create mode 100644 tests/sps/validation/test_fig_old.py diff --git a/tests/sps/validation/test_fig.py b/tests/sps/validation/test_fig.py index a211e3749..2c0ba1d8a 100644 --- a/tests/sps/validation/test_fig.py +++ b/tests/sps/validation/test_fig.py @@ -2,445 +2,434 @@ from lxml import etree from packtools.sps.validation.fig import ArticleFigValidation -from packtools.sps.utils import xml_utils class FigValidationTest(unittest.TestCase): - def test_fig_validation_no_fig(self): + """Test suite for fig validation according to SPS 1.10 specification""" + + def setUp(self): + """Set up common test rules""" self.maxDiff = None + self.rules = { + "article_types_requires": ["research-article"], + "absent_error_level": "WARNING", + "id_error_level": "CRITICAL", + "graphic_error_level": "CRITICAL", + "xlink_href_error_level": "CRITICAL", + "file_extension_error_level": "ERROR", + "fig_type_error_level": "ERROR", + "xml_lang_in_fig_group_error_level": "ERROR", + "accessibility_error_level": "WARNING", + "alt_text_length_error_level": "WARNING", + "allowed_file_extensions": ["jpg", "jpeg", "png", "tif", "tiff"], + "allowed_fig_types": ["graphic", "chart", "diagram", "drawing", "illustration", "map"], + "alt_text_max_length": 120 + } + + def test_fig_validation_no_fig_when_required(self): + """Test validation when fig is required but absent""" xml_tree = etree.fromstring( - '
' + '
' "" "

Some text content without figures.

" "" "
" ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"] - } - ).validate()) - - expected = [ - { - "title": "fig presence", - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": None, - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": "", - 'got_value': None, - 'message': 'Got None, expected ', - "advice": 'article-type=research-article requires . Found 0. Identify the fig or check if article-type is correct', - "data": None, - } - ] - - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - def test_fig_validation_fig_without_id(self): - self.maxDiff = None + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Should return one validation error + self.assertEqual(len(obtained), 1) + self.assertEqual(obtained[0]["title"], "fig presence") + self.assertEqual(obtained[0]["response"], "WARNING") + self.assertEqual(obtained[0]["item"], "fig") + + def test_fig_validation_complete_valid_fig(self): + """Test validation with a complete valid figure""" + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test figure" + '' + "Brief description" + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # All validations should pass + for result in obtained: + self.assertEqual(result["response"], "OK", + f"Validation '{result['title']}' failed: {result.get('advice')}") + + def test_fig_validation_missing_id(self): + """Test Rule 1: Validate @id presence (CRITICAL)""" xml_tree = etree.fromstring( - '
' + '
' "" '' "" - "" - "título da imagem" - "" - "" - '' - '' - "" + "Test" + '' "" "" "
" ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'id', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'id', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'id', - 'got_value': None, - 'message': 'Got None, expected id', - "advice": 'Identify the id', - "data": { - "alternative_parent": "fig", - "id": None, - "type": None, - "label": "Figure 1", - "graphic": 'image1-lowres.png', - "caption": 'título da imagem', - "source_attrib": None, - "alternatives": ["graphic", "graphic"], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': 'png', - }, - } - ] + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) - self.assertEqual(len(obtained), 1) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) + # Find the @id validation + id_validation = [v for v in obtained if v["title"] == "@id"][0] + self.assertEqual(id_validation["response"], "CRITICAL") + self.assertEqual(id_validation["sub_item"], "@id") + self.assertIsNone(id_validation["got_value"]) - def test_fig_validation_fig_without_label(self): - self.maxDiff = None + def test_fig_validation_missing_graphic(self): + """Test Rule 2: Validate presence (CRITICAL)""" xml_tree = etree.fromstring( - '
' + '
' "" '' - "" - "título da imagem" - "" - "" - '' - '' - "" + "" + "Test" "" "" "
" ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'label', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'label', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'label', - 'got_value': None, - 'message': 'Got None, expected label', - "advice": 'Identify the label', - "data": { - "alternative_parent": "fig", - 'id': 'f01', - "type": None, - "label": None, - "graphic": 'image1-lowres.png', - "caption": 'título da imagem', - "source_attrib": None, - "alternatives": ["graphic", "graphic"], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': 'png', - }, - } - ] + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) - self.assertEqual(len(obtained), 1) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) + # Find the validation + graphic_validation = [v for v in obtained if v["title"] == ""][0] + self.assertEqual(graphic_validation["response"], "CRITICAL") + self.assertEqual(graphic_validation["sub_item"], "graphic") + self.assertIsNone(graphic_validation["got_value"]) - def test_fig_validation_fig_without_caption(self): - self.maxDiff = None + def test_fig_validation_missing_xlink_href(self): + """Test Rule 3: Validate @xlink:href in (CRITICAL)""" + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test" + '' + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the @xlink:href validation + xlink_validation = [v for v in obtained if v["title"] == "@xlink:href"][0] + self.assertEqual(xlink_validation["response"], "CRITICAL") + self.assertEqual(xlink_validation["sub_item"], "@xlink:href") + + def test_fig_validation_invalid_file_extension(self): + """Test Rule 4: Validate file extension (ERROR)""" + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test" + '' + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the file extension validation + ext_validation = [v for v in obtained if v["title"] == "file extension"][0] + self.assertEqual(ext_validation["response"], "ERROR") + self.assertEqual(ext_validation["got_value"], "bmp") + + def test_fig_validation_svg_outside_alternatives(self): + """Test Rule 4: SVG is only allowed inside """ + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test" + '' + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the file extension validation + ext_validation = [v for v in obtained if v["title"] == "file extension"][0] + self.assertEqual(ext_validation["response"], "ERROR") + self.assertEqual(ext_validation["got_value"], "svg") + self.assertIn("only allowed inside ", ext_validation["advice"]) + + def test_fig_validation_svg_inside_alternatives(self): + """Test Rule 4: SVG is allowed inside """ xml_tree = etree.fromstring( - '
' + '
' "" '' "" + "Test" "" - '' - '' + '' + '' "" "" "" "
" ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'caption', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'caption', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'caption', - 'got_value': None, - 'message': 'Got None, expected caption', - "advice": 'Identify the caption', - "data": { - "alternative_parent": "fig", - 'id': 'f01', - "type": None, - "label": 'Figure 1', - "graphic": 'image1-lowres.png', - "caption": '', - "source_attrib": None, - "alternatives": ["graphic", "graphic"], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': 'png', - }, - } - ] + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) - self.assertEqual(len(obtained), 1) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) + # Find the file extension validation - should be OK + ext_validation = [v for v in obtained if v["title"] == "file extension"][0] + self.assertEqual(ext_validation["response"], "OK") - def test_fig_validation_fig_without_graphic_and_alternatives(self): - self.maxDiff = None + def test_fig_validation_invalid_fig_type(self): + """Test Rule 5: Validate @fig-type values (ERROR)""" + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test" + '' + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the @fig-type validation + fig_type_validation = [v for v in obtained if v["title"] == "@fig-type"][0] + self.assertEqual(fig_type_validation["response"], "ERROR") + self.assertEqual(fig_type_validation["got_value"], "photo") + + def test_fig_validation_valid_fig_types(self): + """Test Rule 5: All valid @fig-type values""" + valid_types = ["graphic", "chart", "diagram", "drawing", "illustration", "map"] + + for fig_type in valid_types: + with self.subTest(fig_type=fig_type): + xml_tree = etree.fromstring( + f'
' + "" + f'' + "" + "Test" + '' + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the @fig-type validation + fig_type_validation = [v for v in obtained if v["title"] == "@fig-type"][0] + self.assertEqual(fig_type_validation["response"], "OK") + + def test_fig_validation_missing_xml_lang_in_fig_group(self): + """Test Rule 6: Validate @xml:lang in within (ERROR)""" xml_tree = etree.fromstring( - '
' + '
' "" + '' '' + "" + "Test" + '' + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the @xml:lang validation + xml_lang_validation = [v for v in obtained if v["title"] == "@xml:lang in fig-group"][0] + self.assertEqual(xml_lang_validation["response"], "ERROR") + self.assertIsNone(xml_lang_validation["got_value"]) + + def test_fig_validation_with_xml_lang_in_fig_group(self): + """Test Rule 6: @xml:lang present in within """ + xml_tree = etree.fromstring( + '
' + "" + '' + '' + "" + "Test PT" + '' + "" + '' "" - "" - "título da imagem" - "" + "Test EN" + '' "" + "" "" "
" ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'graphic or alternatives', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'graphic or alternatives', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'graphic or alternatives', - 'got_value': None, - 'message': 'Got None, expected graphic or alternatives', - "advice": 'Identify the graphic or alternatives', - "data": { - "alternative_parent": "fig", - 'id': 'f01', - "type": None, - "label": 'Figure 1', - "graphic": None, - "caption": 'título da imagem', - "source_attrib": None, - "alternatives": [], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': None, - }, - }, - { - "title": "file extension", - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": "file extension", - "validation_type": "value in list", - "response": "CRITICAL", - "expected_value": "one of ['tif', 'jpg', 'png', 'svg']", - "got_value": None, - "message": "Got None, expected one of ['tif', 'jpg', 'png', 'svg']", - "advice": "provide a file with one of the following extensions ['tif', 'jpg', 'png', 'svg']", - "data": { - "alternative_parent": "fig", - "id": "f01", - "type": None, - "label": "Figure 1", - "graphic": None, - "caption": "título da imagem", - "source_attrib": None, - "alternatives": [], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "file_extension": None - } - } - ] - - self.assertEqual(len(obtained), 2) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - def test_fig_validation_file_extension(self): - self.maxDiff = None + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find all @xml:lang validations - both should pass + xml_lang_validations = [v for v in obtained if v["title"] == "@xml:lang in fig-group"] + for validation in xml_lang_validations: + self.assertEqual(validation["response"], "OK") + + def test_fig_validation_missing_accessibility(self): + """Test Rule 7: Validate presence of alt-text or long-desc (WARNING)""" + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test" + '' + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the accessibility validation + accessibility_validation = [v for v in obtained if v["title"] == "accessibility"][0] + self.assertEqual(accessibility_validation["response"], "WARNING") + self.assertIsNone(accessibility_validation["got_value"]) + + def test_fig_validation_with_alt_text(self): + """Test Rule 7: Figure with alt-text""" + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test" + '' + "Brief description" + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the accessibility validation + accessibility_validation = [v for v in obtained if v["title"] == "accessibility"][0] + self.assertEqual(accessibility_validation["response"], "OK") + + def test_fig_validation_with_long_desc(self): + """Test Rule 7: Figure with long-desc""" + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "Test" + '' + "Long detailed description for accessibility" + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the accessibility validation + accessibility_validation = [v for v in obtained if v["title"] == "accessibility"][0] + self.assertEqual(accessibility_validation["response"], "OK") + + def test_fig_validation_alt_text_exceeds_length(self): + """Test Rule 8: Validate alt-text character limit (WARNING)""" + long_alt_text = "This is a very long alt text that exceeds the 120 character limit. " \ + "It should trigger a warning because it's too long for accessibility purposes." + + xml_tree = etree.fromstring( + f'
' + "" + '' + "" + "Test" + '' + f"{long_alt_text}" + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the alt-text length validation + alt_text_validation = [v for v in obtained if v["title"] == "alt-text length"][0] + self.assertEqual(alt_text_validation["response"], "WARNING") + self.assertIn("characters", alt_text_validation["got_value"]) + + def test_fig_validation_alt_text_within_length(self): + """Test Rule 8: alt-text within character limit""" xml_tree = etree.fromstring( - """ -
- -

- - - - Título da figura 1 em Português - - - Fonte: IBGE (2018) - -

- -
- """ + '
' + "" + '' + "" + "Test" + '' + "Brief description within limit" + "" + "" + "" + "
" ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": "file extension", - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": "file extension", - "validation_type": "value in list", - "response": 'CRITICAL', - "expected_value": "one of ['tif', 'jpg', 'png', 'svg']", - 'got_value': "bmp", - 'message': "Got bmp, expected one of ['tif', 'jpg', 'png', 'svg']", - "advice": "provide a file with one of the following extensions ['tif', 'jpg', 'png', 'svg']", - "data": { - 'alternative_parent': 'fig', - 'alternatives': [], - 'caption': 'Título da figura 1 em Português', - 'file_extension': 'bmp', - 'graphic': '1234-5678-zwy-12-04-0123-gf02.bmp', - 'id': 'f02', - 'label': 'FIGURE 2', - 'parent': 'article', - 'parent_article_type': 'research-article', - 'parent_id': None, - 'parent_lang': 'pt', - 'source_attrib': 'Fonte: IBGE (2018)', - 'type': 'map' - }, - } - ] - - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + + # Find the alt-text length validation + alt_text_validation = [v for v in obtained if v["title"] == "alt-text length"][0] + self.assertEqual(alt_text_validation["response"], "OK") + + def test_fig_validation_all_file_extensions(self): + """Test Rule 4: All allowed file extensions""" + valid_extensions = ["jpg", "jpeg", "png", "tif", "tiff"] + + for ext in valid_extensions: + with self.subTest(extension=ext): + xml_tree = etree.fromstring( + f'
' + "" + '' + "" + "Test" + f'' + "" + "" + "
" + ) + obtained = list(ArticleFigValidation(xml_tree, self.rules).validate()) + # Find the file extension validation + ext_validation = [v for v in obtained if v["title"] == "file extension"][0] + self.assertEqual(ext_validation["response"], "OK", + f"Extension '{ext}' should be valid") if __name__ == "__main__": diff --git a/tests/sps/validation/test_fig_old.py b/tests/sps/validation/test_fig_old.py new file mode 100644 index 000000000..a211e3749 --- /dev/null +++ b/tests/sps/validation/test_fig_old.py @@ -0,0 +1,447 @@ +import unittest +from lxml import etree + +from packtools.sps.validation.fig import ArticleFigValidation +from packtools.sps.utils import xml_utils + + +class FigValidationTest(unittest.TestCase): + def test_fig_validation_no_fig(self): + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + "

Some text content without figures.

" + "" + "
" + ) + obtained = list(ArticleFigValidation( + xml_tree, + { + "error_level": "WARNING", + "required_error_level": "CRITICAL", + "absent_error_level": "WARNING", + "id_error_level": "CRITICAL", + "label_error_level": "CRITICAL", + "caption_error_level": "CRITICAL", + "content_error_level": "CRITICAL", + "article_types_requires": ["research-article"] + } + ).validate()) + + expected = [ + { + "title": "fig presence", + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "item": "fig", + "sub_item": None, + "validation_type": "exist", + "response": 'CRITICAL', + "expected_value": "", + 'got_value': None, + 'message': 'Got None, expected ', + "advice": 'article-type=research-article requires . Found 0. Identify the fig or check if article-type is correct', + "data": None, + } + ] + + for i, item in enumerate(expected): + with self.subTest(i): + self.assertDictEqual(item, obtained[i]) + + def test_fig_validation_fig_without_id(self): + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "" + "título da imagem" + "" + "" + '' + '' + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation( + xml_tree, + { + "error_level": "WARNING", + "required_error_level": "CRITICAL", + "absent_error_level": "WARNING", + "id_error_level": "CRITICAL", + "label_error_level": "CRITICAL", + "caption_error_level": "CRITICAL", + "content_error_level": "CRITICAL", + "article_types_requires": ["research-article"], + "file_extension_error_level": "CRITICAL", + "allowed file extensions": ["tif", "jpg", "png", "svg"] + } + ).validate()) + + expected = [ + { + "title": 'id', + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "item": "fig", + "sub_item": 'id', + "validation_type": "exist", + "response": 'CRITICAL', + "expected_value": 'id', + 'got_value': None, + 'message': 'Got None, expected id', + "advice": 'Identify the id', + "data": { + "alternative_parent": "fig", + "id": None, + "type": None, + "label": "Figure 1", + "graphic": 'image1-lowres.png', + "caption": 'título da imagem', + "source_attrib": None, + "alternatives": ["graphic", "graphic"], + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + 'file_extension': 'png', + }, + } + ] + + self.assertEqual(len(obtained), 1) + for i, item in enumerate(expected): + with self.subTest(i): + self.assertDictEqual(item, obtained[i]) + + def test_fig_validation_fig_without_label(self): + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "título da imagem" + "" + "" + '' + '' + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation( + xml_tree, + { + "error_level": "WARNING", + "required_error_level": "CRITICAL", + "absent_error_level": "WARNING", + "id_error_level": "CRITICAL", + "label_error_level": "CRITICAL", + "caption_error_level": "CRITICAL", + "content_error_level": "CRITICAL", + "article_types_requires": ["research-article"], + "file_extension_error_level": "CRITICAL", + "allowed file extensions": ["tif", "jpg", "png", "svg"] + } + ).validate()) + + expected = [ + { + "title": 'label', + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "item": "fig", + "sub_item": 'label', + "validation_type": "exist", + "response": 'CRITICAL', + "expected_value": 'label', + 'got_value': None, + 'message': 'Got None, expected label', + "advice": 'Identify the label', + "data": { + "alternative_parent": "fig", + 'id': 'f01', + "type": None, + "label": None, + "graphic": 'image1-lowres.png', + "caption": 'título da imagem', + "source_attrib": None, + "alternatives": ["graphic", "graphic"], + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + 'file_extension': 'png', + }, + } + ] + + self.assertEqual(len(obtained), 1) + for i, item in enumerate(expected): + with self.subTest(i): + self.assertDictEqual(item, obtained[i]) + + def test_fig_validation_fig_without_caption(self): + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "" + '' + '' + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation( + xml_tree, + { + "error_level": "WARNING", + "required_error_level": "CRITICAL", + "absent_error_level": "WARNING", + "id_error_level": "CRITICAL", + "label_error_level": "CRITICAL", + "caption_error_level": "CRITICAL", + "content_error_level": "CRITICAL", + "article_types_requires": ["research-article"], + "file_extension_error_level": "CRITICAL", + "allowed file extensions": ["tif", "jpg", "png", "svg"] + } + ).validate()) + + expected = [ + { + "title": 'caption', + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "item": "fig", + "sub_item": 'caption', + "validation_type": "exist", + "response": 'CRITICAL', + "expected_value": 'caption', + 'got_value': None, + 'message': 'Got None, expected caption', + "advice": 'Identify the caption', + "data": { + "alternative_parent": "fig", + 'id': 'f01', + "type": None, + "label": 'Figure 1', + "graphic": 'image1-lowres.png', + "caption": '', + "source_attrib": None, + "alternatives": ["graphic", "graphic"], + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + 'file_extension': 'png', + }, + } + ] + + self.assertEqual(len(obtained), 1) + for i, item in enumerate(expected): + with self.subTest(i): + self.assertDictEqual(item, obtained[i]) + + def test_fig_validation_fig_without_graphic_and_alternatives(self): + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "" + "título da imagem" + "" + "" + "" + "
" + ) + obtained = list(ArticleFigValidation( + xml_tree, + { + "error_level": "WARNING", + "required_error_level": "CRITICAL", + "absent_error_level": "WARNING", + "id_error_level": "CRITICAL", + "label_error_level": "CRITICAL", + "caption_error_level": "CRITICAL", + "content_error_level": "CRITICAL", + "article_types_requires": ["research-article"], + "file_extension_error_level": "CRITICAL", + "allowed file extensions": ["tif", "jpg", "png", "svg"] + } + ).validate()) + + expected = [ + { + "title": 'graphic or alternatives', + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "item": "fig", + "sub_item": 'graphic or alternatives', + "validation_type": "exist", + "response": 'CRITICAL', + "expected_value": 'graphic or alternatives', + 'got_value': None, + 'message': 'Got None, expected graphic or alternatives', + "advice": 'Identify the graphic or alternatives', + "data": { + "alternative_parent": "fig", + 'id': 'f01', + "type": None, + "label": 'Figure 1', + "graphic": None, + "caption": 'título da imagem', + "source_attrib": None, + "alternatives": [], + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + 'file_extension': None, + }, + }, + { + "title": "file extension", + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "item": "fig", + "sub_item": "file extension", + "validation_type": "value in list", + "response": "CRITICAL", + "expected_value": "one of ['tif', 'jpg', 'png', 'svg']", + "got_value": None, + "message": "Got None, expected one of ['tif', 'jpg', 'png', 'svg']", + "advice": "provide a file with one of the following extensions ['tif', 'jpg', 'png', 'svg']", + "data": { + "alternative_parent": "fig", + "id": "f01", + "type": None, + "label": "Figure 1", + "graphic": None, + "caption": "título da imagem", + "source_attrib": None, + "alternatives": [], + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "file_extension": None + } + } + ] + + self.assertEqual(len(obtained), 2) + for i, item in enumerate(expected): + with self.subTest(i): + self.assertDictEqual(item, obtained[i]) + + def test_fig_validation_file_extension(self): + self.maxDiff = None + xml_tree = etree.fromstring( + """ +
+ +

+ + + + Título da figura 1 em Português + + + Fonte: IBGE (2018) + +

+ +
+ """ + ) + obtained = list(ArticleFigValidation( + xml_tree, + { + "error_level": "WARNING", + "required_error_level": "CRITICAL", + "absent_error_level": "WARNING", + "id_error_level": "CRITICAL", + "label_error_level": "CRITICAL", + "caption_error_level": "CRITICAL", + "content_error_level": "CRITICAL", + "article_types_requires": ["research-article"], + "file_extension_error_level": "CRITICAL", + "allowed file extensions": ["tif", "jpg", "png", "svg"] + } + ).validate()) + + expected = [ + { + "title": "file extension", + "parent": "article", + "parent_id": None, + "parent_article_type": "research-article", + "parent_lang": "pt", + "item": "fig", + "sub_item": "file extension", + "validation_type": "value in list", + "response": 'CRITICAL', + "expected_value": "one of ['tif', 'jpg', 'png', 'svg']", + 'got_value': "bmp", + 'message': "Got bmp, expected one of ['tif', 'jpg', 'png', 'svg']", + "advice": "provide a file with one of the following extensions ['tif', 'jpg', 'png', 'svg']", + "data": { + 'alternative_parent': 'fig', + 'alternatives': [], + 'caption': 'Título da figura 1 em Português', + 'file_extension': 'bmp', + 'graphic': '1234-5678-zwy-12-04-0123-gf02.bmp', + 'id': 'f02', + 'label': 'FIGURE 2', + 'parent': 'article', + 'parent_article_type': 'research-article', + 'parent_id': None, + 'parent_lang': 'pt', + 'source_attrib': 'Fonte: IBGE (2018)', + 'type': 'map' + }, + } + ] + + for i, item in enumerate(expected): + with self.subTest(i): + self.assertDictEqual(item, obtained[i]) + + + +if __name__ == "__main__": + unittest.main() From 1f487d57c8fb5ea4ab9d48773b550785d0f1d280 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:18:18 +0000 Subject: [PATCH 4/6] Remove backup test file Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- tests/sps/validation/test_fig_old.py | 447 --------------------------- 1 file changed, 447 deletions(-) delete mode 100644 tests/sps/validation/test_fig_old.py diff --git a/tests/sps/validation/test_fig_old.py b/tests/sps/validation/test_fig_old.py deleted file mode 100644 index a211e3749..000000000 --- a/tests/sps/validation/test_fig_old.py +++ /dev/null @@ -1,447 +0,0 @@ -import unittest -from lxml import etree - -from packtools.sps.validation.fig import ArticleFigValidation -from packtools.sps.utils import xml_utils - - -class FigValidationTest(unittest.TestCase): - def test_fig_validation_no_fig(self): - self.maxDiff = None - xml_tree = etree.fromstring( - '
' - "" - "

Some text content without figures.

" - "" - "
" - ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"] - } - ).validate()) - - expected = [ - { - "title": "fig presence", - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": None, - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": "", - 'got_value': None, - 'message': 'Got None, expected ', - "advice": 'article-type=research-article requires . Found 0. Identify the fig or check if article-type is correct', - "data": None, - } - ] - - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - def test_fig_validation_fig_without_id(self): - self.maxDiff = None - xml_tree = etree.fromstring( - '
' - "" - '' - "" - "" - "título da imagem" - "" - "" - '' - '' - "" - "" - "" - "
" - ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'id', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'id', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'id', - 'got_value': None, - 'message': 'Got None, expected id', - "advice": 'Identify the id', - "data": { - "alternative_parent": "fig", - "id": None, - "type": None, - "label": "Figure 1", - "graphic": 'image1-lowres.png', - "caption": 'título da imagem', - "source_attrib": None, - "alternatives": ["graphic", "graphic"], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': 'png', - }, - } - ] - - self.assertEqual(len(obtained), 1) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - def test_fig_validation_fig_without_label(self): - self.maxDiff = None - xml_tree = etree.fromstring( - '
' - "" - '' - "" - "título da imagem" - "" - "" - '' - '' - "" - "" - "" - "
" - ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'label', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'label', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'label', - 'got_value': None, - 'message': 'Got None, expected label', - "advice": 'Identify the label', - "data": { - "alternative_parent": "fig", - 'id': 'f01', - "type": None, - "label": None, - "graphic": 'image1-lowres.png', - "caption": 'título da imagem', - "source_attrib": None, - "alternatives": ["graphic", "graphic"], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': 'png', - }, - } - ] - - self.assertEqual(len(obtained), 1) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - def test_fig_validation_fig_without_caption(self): - self.maxDiff = None - xml_tree = etree.fromstring( - '
' - "" - '' - "" - "" - '' - '' - "" - "" - "" - "
" - ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'caption', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'caption', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'caption', - 'got_value': None, - 'message': 'Got None, expected caption', - "advice": 'Identify the caption', - "data": { - "alternative_parent": "fig", - 'id': 'f01', - "type": None, - "label": 'Figure 1', - "graphic": 'image1-lowres.png', - "caption": '', - "source_attrib": None, - "alternatives": ["graphic", "graphic"], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': 'png', - }, - } - ] - - self.assertEqual(len(obtained), 1) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - def test_fig_validation_fig_without_graphic_and_alternatives(self): - self.maxDiff = None - xml_tree = etree.fromstring( - '
' - "" - '' - "" - "" - "título da imagem" - "" - "" - "" - "
" - ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": 'graphic or alternatives', - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": 'graphic or alternatives', - "validation_type": "exist", - "response": 'CRITICAL', - "expected_value": 'graphic or alternatives', - 'got_value': None, - 'message': 'Got None, expected graphic or alternatives', - "advice": 'Identify the graphic or alternatives', - "data": { - "alternative_parent": "fig", - 'id': 'f01', - "type": None, - "label": 'Figure 1', - "graphic": None, - "caption": 'título da imagem', - "source_attrib": None, - "alternatives": [], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - 'file_extension': None, - }, - }, - { - "title": "file extension", - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": "file extension", - "validation_type": "value in list", - "response": "CRITICAL", - "expected_value": "one of ['tif', 'jpg', 'png', 'svg']", - "got_value": None, - "message": "Got None, expected one of ['tif', 'jpg', 'png', 'svg']", - "advice": "provide a file with one of the following extensions ['tif', 'jpg', 'png', 'svg']", - "data": { - "alternative_parent": "fig", - "id": "f01", - "type": None, - "label": "Figure 1", - "graphic": None, - "caption": "título da imagem", - "source_attrib": None, - "alternatives": [], - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "file_extension": None - } - } - ] - - self.assertEqual(len(obtained), 2) - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - def test_fig_validation_file_extension(self): - self.maxDiff = None - xml_tree = etree.fromstring( - """ -
- -

- - - - Título da figura 1 em Português - - - Fonte: IBGE (2018) - -

- -
- """ - ) - obtained = list(ArticleFigValidation( - xml_tree, - { - "error_level": "WARNING", - "required_error_level": "CRITICAL", - "absent_error_level": "WARNING", - "id_error_level": "CRITICAL", - "label_error_level": "CRITICAL", - "caption_error_level": "CRITICAL", - "content_error_level": "CRITICAL", - "article_types_requires": ["research-article"], - "file_extension_error_level": "CRITICAL", - "allowed file extensions": ["tif", "jpg", "png", "svg"] - } - ).validate()) - - expected = [ - { - "title": "file extension", - "parent": "article", - "parent_id": None, - "parent_article_type": "research-article", - "parent_lang": "pt", - "item": "fig", - "sub_item": "file extension", - "validation_type": "value in list", - "response": 'CRITICAL', - "expected_value": "one of ['tif', 'jpg', 'png', 'svg']", - 'got_value': "bmp", - 'message': "Got bmp, expected one of ['tif', 'jpg', 'png', 'svg']", - "advice": "provide a file with one of the following extensions ['tif', 'jpg', 'png', 'svg']", - "data": { - 'alternative_parent': 'fig', - 'alternatives': [], - 'caption': 'Título da figura 1 em Português', - 'file_extension': 'bmp', - 'graphic': '1234-5678-zwy-12-04-0123-gf02.bmp', - 'id': 'f02', - 'label': 'FIGURE 2', - 'parent': 'article', - 'parent_article_type': 'research-article', - 'parent_id': None, - 'parent_lang': 'pt', - 'source_attrib': 'Fonte: IBGE (2018)', - 'type': 'map' - }, - } - ] - - for i, item in enumerate(expected): - with self.subTest(i): - self.assertDictEqual(item, obtained[i]) - - - -if __name__ == "__main__": - unittest.main() From d35061c357b1e834a9e795e87466d0cbb54864fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:19:38 +0000 Subject: [PATCH 5/6] Address code review feedback: improve code consistency and clarity Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- packtools/sps/validation/fig.py | 26 +++++--------------------- tests/sps/validation/test_fig.py | 6 ++++-- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/packtools/sps/validation/fig.py b/packtools/sps/validation/fig.py index 546d17479..2d36503b5 100644 --- a/packtools/sps/validation/fig.py +++ b/packtools/sps/validation/fig.py @@ -72,18 +72,18 @@ def validate(self): # P0 - ERROR: Rule 4 - Validate file extension yield self.validate_file_extension() - # P0 - ERROR: Rule 5 - Validate @fig-type values - if self.data.get("type"): # Only validate if fig-type is present + # P0 - ERROR: Rule 5 - Validate @fig-type values (only if present) + if self.data.get("type"): yield self.validate_fig_type() - # P1 - ERROR: Rule 6 - Validate @xml:lang in fig-group + # P1 - ERROR: Rule 6 - Validate @xml:lang in fig-group (only if in fig-group) if self.data.get("parent_name") == "fig-group": yield self.validate_xml_lang_in_fig_group() # P1 - WARNING: Rule 7 - Validate accessibility (alt-text or long-desc) yield self.validate_accessibility() - # P1 - WARNING: Rule 8 - Validate alt-text length + # P1 - WARNING: Rule 8 - Validate alt-text length (only if alt-text present) if self.data.get("graphic_alt_text"): yield self.validate_alt_text_length() @@ -186,7 +186,7 @@ def validate_fig_type(self): """Rule 5: Validate @fig-type values (ERROR)""" fig_type = self.data.get("type") allowed_types = self.rules["allowed_fig_types"] - is_valid = fig_type in allowed_types if fig_type else True # If not present, it's valid + is_valid = fig_type in allowed_types return build_response( title="@fig-type", @@ -245,22 +245,6 @@ def validate_alt_text_length(self): """Rule 8: Validate alt-text character limit (WARNING)""" alt_text = self.data.get("graphic_alt_text") max_length = self.rules["alt_text_max_length"] - - if not alt_text: - return build_response( - title="alt-text length", - parent=self.data, - item="fig", - sub_item="alt-text length", - validation_type="format", - is_valid=True, - expected=f"≤ {max_length} characters", - obtained=None, - advice=None, - data=self.data, - error_level=self.rules["alt_text_length_error_level"], - ) - current_length = len(alt_text) is_valid = current_length <= max_length diff --git a/tests/sps/validation/test_fig.py b/tests/sps/validation/test_fig.py index 2c0ba1d8a..eab14c2b3 100644 --- a/tests/sps/validation/test_fig.py +++ b/tests/sps/validation/test_fig.py @@ -360,8 +360,10 @@ def test_fig_validation_with_long_desc(self): def test_fig_validation_alt_text_exceeds_length(self): """Test Rule 8: Validate alt-text character limit (WARNING)""" - long_alt_text = "This is a very long alt text that exceeds the 120 character limit. " \ - "It should trigger a warning because it's too long for accessibility purposes." + long_alt_text = ( + "This is a very long alt text that exceeds the 120 character limit. " + "It should trigger a warning because it's too long for accessibility purposes." + ) xml_tree = etree.fromstring( f'
Date: Fri, 13 Feb 2026 12:22:59 +0000 Subject: [PATCH 6/6] Update model tests to include new fields (xml_lang, parent_name, graphic_alt_text, graphic_long_desc) Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- tests/sps/models/test_fig.py | 66 ++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/tests/sps/models/test_fig.py b/tests/sps/models/test_fig.py index 9e4e60b3b..4af9ca8bf 100644 --- a/tests/sps/models/test_fig.py +++ b/tests/sps/models/test_fig.py @@ -112,13 +112,17 @@ def test_data(self): "graphic": "1234-5678-zwy-12-04-0123-gf02.tif", "caption": "Título da figura", "source_attrib": "Fonte: IBGE (2018)", - "alternatives": [ + "alternative_elements": [ "graphic", "graphic", "textual-alternative", "media", ], "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", } self.assertDictEqual(self.fig_obj.data, expected_data) @@ -227,7 +231,7 @@ def test_get_all_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf02.tif", "caption": "Título da figura 1 em Português", "source_attrib": "Fonte: IBGE (2018)", - "alternatives": [ + "alternative_elements": [ "graphic", "graphic", "textual-alternative", @@ -238,6 +242,10 @@ def test_get_all_figs(self): "parent_lang": "pt", "parent_article_type": "research-article", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", }, { "alternative_parent": "fig", @@ -247,7 +255,7 @@ def test_get_all_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf03.tif", "caption": "Título da figura 2 em Português", "source_attrib": "Fonte: IBGE (2019)", - "alternatives": [ + "alternative_elements": [ "graphic", "graphic", "textual-alternative", @@ -258,6 +266,10 @@ def test_get_all_figs(self): "parent_lang": "pt", "parent_article_type": "research-article", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", }, { "alternative_parent": "fig", @@ -267,12 +279,16 @@ def test_get_all_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf01.tif", "caption": "Title of Map 1", "source_attrib": None, - "alternatives": [], + "alternative_elements": [], "parent": "sub-article", "parent_id": "TRen", "parent_lang": "en", "parent_article_type": "translation", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", }, { "alternative_parent": "fig", @@ -282,15 +298,19 @@ def test_get_all_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf04.tif", "caption": "Title of Map 2", "source_attrib": None, - "alternatives": [], + "alternative_elements": [], "parent": "sub-article", "parent_id": "TRen", "parent_lang": "en", "parent_article_type": "translation", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", }, { - 'alternatives': [], + 'alternative_elements': [], 'alternative_parent': 'fig', 'caption': 'Chart Showing Additional Data', 'id': 'sf1', @@ -303,6 +323,10 @@ def test_get_all_figs(self): 'parent_lang': 'en', 'source_attrib': 'Data Source: Experimental Data 2020', "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "sec", } ] @@ -325,7 +349,7 @@ def test_get_article_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf02.tif", "caption": "Título da figura 1 em Português", "source_attrib": "Fonte: IBGE (2018)", - "alternatives": [ + "alternative_elements": [ "graphic", "graphic", "textual-alternative", @@ -336,6 +360,10 @@ def test_get_article_figs(self): "parent_lang": "pt", "parent_article_type": "research-article", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", }, { "alternative_parent": "fig", @@ -345,7 +373,7 @@ def test_get_article_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf03.tif", "caption": "Título da figura 2 em Português", "source_attrib": "Fonte: IBGE (2019)", - "alternatives": [ + "alternative_elements": [ "graphic", "graphic", "textual-alternative", @@ -356,6 +384,10 @@ def test_get_article_figs(self): "parent_lang": "pt", "parent_article_type": "research-article", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", } ] @@ -377,12 +409,16 @@ def test_get_sub_article_translation_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf01.tif", "caption": "Title of Map 1", "source_attrib": None, - "alternatives": [], + "alternative_elements": [], "parent": "sub-article", "parent_id": "TRen", "parent_lang": "en", "parent_article_type": "translation", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", }, { "alternative_parent": "fig", @@ -392,12 +428,16 @@ def test_get_sub_article_translation_figs(self): "graphic": "1234-5678-zwy-12-04-0123-gf04.tif", "caption": "Title of Map 2", "source_attrib": None, - "alternatives": [], + "alternative_elements": [], "parent": "sub-article", "parent_id": "TRen", "parent_lang": "en", "parent_article_type": "translation", "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "p", } ] @@ -412,7 +452,7 @@ def test_get_sub_article_non_translation_figs(self): expected = [ { - 'alternatives': [], + 'alternative_elements': [], 'alternative_parent': 'fig', 'caption': 'Chart Showing Additional Data', 'id': 'sf1', @@ -425,6 +465,10 @@ def test_get_sub_article_non_translation_figs(self): 'parent_lang': 'en', 'source_attrib': 'Data Source: Experimental Data 2020', "file_extension": "tif", + "graphic_alt_text": None, + "graphic_long_desc": None, + "xml_lang": None, + "parent_name": "sec", } ]