From 32c782e7df486ead1c6d9d41b2803282a45faed1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:44:20 +0000 Subject: [PATCH 1/4] Initial plan From c3d321eb0b39106771090e9ff7d0d44fe5c98e02 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:46:14 +0000 Subject: [PATCH 2/4] Initial plan for adding validate_mathml_recommendation Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- src/scielo-scholarly-data | 1 + 1 file changed, 1 insertion(+) create mode 160000 src/scielo-scholarly-data diff --git a/src/scielo-scholarly-data b/src/scielo-scholarly-data new file mode 160000 index 000000000..a2899ce8a --- /dev/null +++ b/src/scielo-scholarly-data @@ -0,0 +1 @@ +Subproject commit a2899ce8a1fa77396c516640d36686351210d606 From cd1cd11d05b3aa11f6ac94c7ebd8425901dff289 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:47:56 +0000 Subject: [PATCH 3/4] Add validate_mathml_recommendation to formula validations Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com> --- packtools/sps/validation/formula.py | 84 +++++++++ .../sps/validation_rules/formula_rules.json | 2 + tests/sps/validation/test_formula.py | 169 ++++++++++++++++++ 3 files changed, 255 insertions(+) diff --git a/packtools/sps/validation/formula.py b/packtools/sps/validation/formula.py index 52364f263..987edc85c 100644 --- a/packtools/sps/validation/formula.py +++ b/packtools/sps/validation/formula.py @@ -83,6 +83,7 @@ def get_default_params(self): "codification_error_level": "CRITICAL", "mml_math_id_error_level": "CRITICAL", "mml_math_id_prefix_error_level": "ERROR", + "mathml_error_level": "WARNING", "alternatives_error_level": "CRITICAL" } @@ -101,6 +102,7 @@ def validate(self): self.validate_codification, self.validate_mml_math_id, self.validate_mml_math_id_prefix, + self.validate_mathml_recommendation, self.validate_alternatives ] return [response for validate in validations if (response := validate())] @@ -290,6 +292,46 @@ def validate_mml_math_id_prefix(self): advice_params={"mml_id": mml_math_id, "formula_id": item_id}, ) + def validate_mathml_recommendation(self): + """ + Validates and recommends MathML when only TeX math is present for accessibility. + + Returns: + dict or None: A validation result dictionary if the validation fails; otherwise, None. + """ + has_mml_math = bool(self.data.get("mml_math")) + has_tex_math = bool(self.data.get("tex_math")) + + # Return None if there's no codification at all + if not has_mml_math and not has_tex_math: + return None + + # Only warn if there's tex-math but no mml:math + if has_tex_math and not has_mml_math: + item_id = self.data.get("id") + is_valid = False + expected = "mml:math" + obtained = "tex-math" + + return build_response( + title="MathML recommendation", + parent=self.data, + item="mml:math", + sub_item=None, + validation_type="exist", + is_valid=is_valid, + expected=expected, + obtained=obtained, + advice=_('For accessibility, consider adding in . MathML improves accessibility for screen readers. Consult SPS documentation for more detail.').format(formula_id=item_id), + data=self.data, + error_level=self.rules["mathml_error_level"], + advice_text=_('For accessibility, consider adding in . MathML improves accessibility for screen readers. Consult SPS documentation for more detail.'), + advice_params={"formula_id": item_id}, + ) + + # Otherwise, it's valid (has mml:math or both) + return None + def validate_alternatives(self): """ Validates the presence of the 'alternatives' attribute in a element. @@ -432,6 +474,7 @@ def get_default_params(self): "codification_error_level": "CRITICAL", "mml_math_id_error_level": "CRITICAL", "mml_math_id_prefix_error_level": "ERROR", + "mathml_error_level": "WARNING", "alternatives_error_level": "CRITICAL" } @@ -449,6 +492,7 @@ def validate(self): self.validate_codification, self.validate_mml_math_id, self.validate_mml_math_id_prefix, + self.validate_mathml_recommendation, self.validate_alternatives ] return [response for validate in validations if (response := validate())] @@ -611,6 +655,46 @@ def validate_mml_math_id_prefix(self): advice_params={"mml_id": mml_math_id, "formula_id": item_id}, ) + def validate_mathml_recommendation(self): + """ + Validates and recommends MathML when only TeX math is present for accessibility. + + Returns: + dict or None: A validation result dictionary if the validation fails; otherwise, None. + """ + has_mml_math = bool(self.data.get("mml_math")) + has_tex_math = bool(self.data.get("tex_math")) + + # Return None if there's no codification at all + if not has_mml_math and not has_tex_math: + return None + + # Only warn if there's tex-math but no mml:math + if has_tex_math and not has_mml_math: + item_id = self.data.get("id") + is_valid = False + expected = "mml:math" + obtained = "tex-math" + + return build_response( + title="MathML recommendation", + parent=self.data, + item="mml:math", + sub_item=None, + validation_type="exist", + is_valid=is_valid, + expected=expected, + obtained=obtained, + advice=_('For accessibility, consider adding in . MathML improves accessibility for screen readers. Consult SPS documentation for more detail.').format(formula_id=item_id), + data=self.data, + error_level=self.rules["mathml_error_level"], + advice_text=_('For accessibility, consider adding in . MathML improves accessibility for screen readers. Consult SPS documentation for more detail.'), + advice_params={"formula_id": item_id}, + ) + + # Otherwise, it's valid (has mml:math or both) + return None + def validate_alternatives(self): """ Validates the presence of alternatives in a element. diff --git a/packtools/sps/validation_rules/formula_rules.json b/packtools/sps/validation_rules/formula_rules.json index 38f9b8f8c..3038996d9 100644 --- a/packtools/sps/validation_rules/formula_rules.json +++ b/packtools/sps/validation_rules/formula_rules.json @@ -7,6 +7,7 @@ "codification_error_level": "CRITICAL", "mml_math_id_error_level": "CRITICAL", "mml_math_id_prefix_error_level": "ERROR", + "mathml_error_level": "WARNING", "alternatives_error_level": "CRITICAL" }, "inline_formula_rules": { @@ -16,6 +17,7 @@ "codification_error_level": "CRITICAL", "mml_math_id_error_level": "CRITICAL", "mml_math_id_prefix_error_level": "ERROR", + "mathml_error_level": "WARNING", "alternatives_error_level": "CRITICAL" } } diff --git a/tests/sps/validation/test_formula.py b/tests/sps/validation/test_formula.py index 400ae1591..4b44190fe 100644 --- a/tests/sps/validation/test_formula.py +++ b/tests/sps/validation/test_formula.py @@ -608,3 +608,172 @@ def test_validate_alternatives_not_required_in_disp_formula(self): self.assertEqual(error["response"], "CRITICAL") self.assertEqual(error["got_value"], "alternatives") self.assertIn("Remove the ", error["advice"]) + + def test_validate_mathml_recommendation_in_disp_formula_with_only_tex(self): + """Test MathML recommendation when disp-formula has only tex-math""" + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + '\\[ E = mc^2 \\]' + "" + "" + "
" + ) + obtained = list( + ArticleDispFormulaValidation( + xml_tree=xml_tree, rules={"mathml_error_level": "WARNING"} + ).validate() + ) + + # Deve retornar aviso recomendando MathML + warnings = [item for item in obtained if item["title"] == "MathML recommendation"] + self.assertEqual(len(warnings), 1) + + warning = warnings[0] + self.assertEqual(warning["response"], "WARNING") + self.assertEqual(warning["expected_value"], "mml:math") + self.assertEqual(warning["got_value"], "tex-math") + self.assertIn("accessibility", warning["advice"]) + self.assertIn("mml:math", warning["advice"]) + + def test_validate_mathml_recommendation_in_disp_formula_with_mml(self): + """Test that no warning is issued when disp-formula has mml:math""" + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + '' + "" + "E=mc2" + "" + "" + "" + "" + "
" + ) + obtained = list( + ArticleDispFormulaValidation( + xml_tree=xml_tree, rules={"mathml_error_level": "WARNING"} + ).validate() + ) + + # Não deve retornar aviso de MathML + warnings = [item for item in obtained if item["title"] == "MathML recommendation"] + self.assertEqual(len(warnings), 0) + + def test_validate_mathml_recommendation_in_disp_formula_with_both(self): + """Test that no warning is issued when disp-formula has both tex-math and mml:math""" + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + "" + '' + "" + "E=mc2" + "" + "" + '\\[ E = mc^2 \\]' + "" + "" + "" + "
" + ) + obtained = list( + ArticleDispFormulaValidation( + xml_tree=xml_tree, rules={"mathml_error_level": "WARNING"} + ).validate() + ) + + # Não deve retornar aviso de MathML + warnings = [item for item in obtained if item["title"] == "MathML recommendation"] + self.assertEqual(len(warnings), 0) + + def test_validate_mathml_recommendation_in_inline_formula_with_only_tex(self): + """Test MathML recommendation when inline-formula has only tex-math""" + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '

Some text with ' + 'x^2' + " in text.

" + "" + "
" + ) + obtained = list( + ArticleInlineFormulaValidation( + xml_tree=xml_tree, rules={"mathml_error_level": "WARNING"} + ).validate() + ) + + # Deve retornar aviso recomendando MathML + warnings = [item for item in obtained if item["title"] == "MathML recommendation"] + self.assertEqual(len(warnings), 1) + + warning = warnings[0] + self.assertEqual(warning["response"], "WARNING") + self.assertEqual(warning["expected_value"], "mml:math") + self.assertEqual(warning["got_value"], "tex-math") + self.assertIn("accessibility", warning["advice"]) + self.assertIn("mml:math", warning["advice"]) + + def test_validate_mathml_recommendation_in_inline_formula_with_mml(self): + """Test that no warning is issued when inline-formula has mml:math""" + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '

Some text with ' + '' + "x2" + "" + " in text.

" + "" + "
" + ) + obtained = list( + ArticleInlineFormulaValidation( + xml_tree=xml_tree, rules={"mathml_error_level": "WARNING"} + ).validate() + ) + + # Não deve retornar aviso de MathML + warnings = [item for item in obtained if item["title"] == "MathML recommendation"] + self.assertEqual(len(warnings), 0) + + def test_validate_mathml_recommendation_returns_none_without_codification(self): + """Test that mathml recommendation returns None when there's no codification at all""" + self.maxDiff = None + xml_tree = etree.fromstring( + '
' + "" + '' + "" + '' + "" + "" + "
" + ) + obtained = list( + ArticleDispFormulaValidation( + xml_tree=xml_tree, rules={"mathml_error_level": "WARNING"} + ).validate() + ) + + # Não deve retornar aviso de MathML (já retornará erro de codificação) + warnings = [item for item in obtained if item["title"] == "MathML recommendation"] + self.assertEqual(len(warnings), 0) From 9577d6d7f39f0c57e6b43f973715f4b5cdc2b582 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:48:48 +0000 Subject: [PATCH 4/4] Remove accidentally added submodule and update gitignore --- .gitignore | 1 + src/scielo-scholarly-data | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 160000 src/scielo-scholarly-data diff --git a/.gitignore b/.gitignore index e330edc72..22b92b87a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ nosetests.xml .venv .idea +src/scielo-scholarly-data diff --git a/src/scielo-scholarly-data b/src/scielo-scholarly-data deleted file mode 160000 index a2899ce8a..000000000 --- a/src/scielo-scholarly-data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a2899ce8a1fa77396c516640d36686351210d606