From ab46e8187d021b83ebf46167e006479b73cf93cb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:17:46 +0000
Subject: [PATCH 1/2] Initial plan
From 9532b87e8a8d98915fc13710649af9960a642d88 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 13 Mar 2026 14:30:17 +0000
Subject: [PATCH 2/2] Add table-wrap validations for NISO JATS rules (tr, th,
td, tbody) and label-or-caption combined check
Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
---
packtools/sps/models/tablewrap.py | 40 +
packtools/sps/validation/tablewrap.py | 212 +++--
.../sps/validation_rules/tablewrap_rules.json | 7 +-
tests/sps/models/test_tablewrap.py | 54 ++
tests/sps/validation/test_tablewrap.py | 813 ++++++++++++------
5 files changed, 806 insertions(+), 320 deletions(-)
diff --git a/packtools/sps/models/tablewrap.py b/packtools/sps/models/tablewrap.py
index 68102e5b7..3c09b968c 100644
--- a/packtools/sps/models/tablewrap.py
+++ b/packtools/sps/models/tablewrap.py
@@ -106,6 +106,42 @@ def graphic_long_desc(self):
return long_desc_elem.text
return None
+ @property
+ def has_tr_in_table(self):
+ """Check if
has direct children (invalid per NISO JATS)."""
+ table = self.element.find(".//table")
+ if table is not None:
+ return table.find("tr") is not None
+ return False
+
+ @property
+ def has_th_outside_thead(self):
+ """Check if appears outside of ."""
+ table = self.element.find(".//table")
+ if table is not None:
+ all_th = table.findall(".//th")
+ thead_th = table.findall(".//thead//th")
+ return len(all_th) > len(thead_th)
+ return False
+
+ @property
+ def has_td_outside_tbody(self):
+ """Check if appears outside of ."""
+ table = self.element.find(".//table")
+ if table is not None:
+ all_td = table.findall(".//td")
+ tbody_td = table.findall(".//tbody//td")
+ return len(all_td) > len(tbody_td)
+ return False
+
+ @property
+ def has_tbody(self):
+ """Check if has a element."""
+ table = self.element.find(".//table")
+ if table is not None:
+ return table.find(".//tbody") is not None
+ return False
+
@property
def data(self):
return {
@@ -119,6 +155,10 @@ def data(self):
"graphic": self.graphic,
"graphic_alt_text": self.graphic_alt_text,
"graphic_long_desc": self.graphic_long_desc,
+ "has_tr_in_table": self.has_tr_in_table,
+ "has_th_outside_thead": self.has_th_outside_thead,
+ "has_td_outside_tbody": self.has_td_outside_tbody,
+ "has_tbody": self.has_tbody,
}
diff --git a/packtools/sps/validation/tablewrap.py b/packtools/sps/validation/tablewrap.py
index 3ce8d3277..75977e27e 100644
--- a/packtools/sps/validation/tablewrap.py
+++ b/packtools/sps/validation/tablewrap.py
@@ -79,10 +79,15 @@ def get_default_params(self):
return {
"absent_error_level": "WARNING",
"id_error_level": "CRITICAL",
+ "label_or_caption_error_level": "CRITICAL",
"label_error_level": "CRITICAL",
"caption_error_level": "CRITICAL",
"table_error_level": "CRITICAL",
- "alternatives_error_level": "CRITICAL"
+ "alternatives_error_level": "CRITICAL",
+ "tr_in_table_error_level": "ERROR",
+ "th_in_thead_error_level": "ERROR",
+ "td_in_tbody_error_level": "ERROR",
+ "tbody_error_level": "WARNING",
}
def validate(self):
@@ -92,10 +97,13 @@ def validate(self):
"""
validations = [
self.validate_id,
- self.validate_label,
- self.validate_caption,
+ self.validate_label_or_caption,
self.validate_table,
self.validate_alternatives,
+ self.validate_tr_not_in_table,
+ self.validate_th_in_thead,
+ self.validate_td_in_tbody,
+ self.validate_tbody,
]
return [response for validate in validations if (response := validate())]
@@ -128,20 +136,27 @@ def validate_id(self):
advice_params={},
)
- def validate_label(self):
+ def validate_label_or_caption(self):
"""
- Validates the presence of label in the .
+ Validates the presence of or in the .
+ At least one of or with must be present.
Returns:
The validation result in the expected format.
"""
label = self.data.get("label")
- is_valid = bool(label)
- table_id = self.data.get("table_wrap_id")
- advice = f'Wrap each label with inside {self.xml}. Consult SPS documentation for more detail.'
+ caption = self.data.get("caption")
+ is_valid = bool(label) or bool(caption)
+ obtained = []
+ if label:
+ obtained.append(f"label={label}")
+ if caption:
+ obtained.append(f"caption={caption}")
+ obtained_str = ", ".join(obtained) if obtained else None
+ advice = f'Add or with inside {self.xml}. Consult SPS documentation for more detail.'
return build_response(
- title="label",
+ title="label or caption",
parent={
"parent": self.data.get("parent"),
"parent_id": self.data.get("parent_id"),
@@ -149,46 +164,15 @@ def validate_label(self):
"parent_lang": self.data.get("parent_lang"),
},
item="table-wrap",
- sub_item="label",
+ sub_item="label or caption",
validation_type="exist",
is_valid=is_valid,
- expected="label",
- obtained=label,
+ expected=" or with ",
+ obtained=obtained_str,
advice=advice,
data=self.data,
- error_level=self.rules["label_error_level"],
- advice_text='Wrap each label with inside {xml}. Consult SPS documentation for more detail.',
- advice_params={"xml": self.xml},
- )
-
- def validate_caption(self):
- """
- Validates the presence of caption in the .
-
- Returns:
- The validation result in the expected format.
- """
- caption = self.data.get("caption")
- is_valid = bool(caption)
- table_id = self.data.get("table_wrap_id")
- return build_response(
- title="caption",
- parent={
- "parent": self.data.get("parent"),
- "parent_id": self.data.get("parent_id"),
- "parent_article_type": self.data.get("parent_article_type"),
- "parent_lang": self.data.get("parent_lang"),
- },
- item="table-wrap",
- sub_item="caption",
- validation_type="exist",
- is_valid=is_valid,
- expected="caption",
- obtained=caption,
- advice=f'Wrap each caption with inside {self.xml}. Consult SPS documentation for more detail.',
- data=self.data,
- error_level=self.rules["caption_error_level"],
- advice_text='Wrap each caption with inside {xml}. Consult SPS documentation for more detail.',
+ error_level=self.rules["label_or_caption_error_level"],
+ advice_text='Add or with inside {xml}. Consult SPS documentation for more detail.',
advice_params={"xml": self.xml},
)
@@ -201,7 +185,6 @@ def validate_table(self):
"""
table = self.data.get("table")
is_valid = bool(table)
- table_id = self.data.get("table_wrap_id")
return build_response(
title="table",
parent={
@@ -230,11 +213,9 @@ def validate_alternatives(self):
Returns:
The validation result in the expected format.
"""
- graphic = 1 if self.data.get("graphic") else 0 # self.data.get("graphic") retorna uma referência para uma representação da tabela, se houver
- table = 1 if self.data.get("table") else 0 # self.data.get("table") retorna uma codificação de tabela, se houver
- alternatives = self.data.get("alternative_elements") # uma lista com as tags internas à
-
- table_id = self.data.get("table_wrap_id")
+ graphic = 1 if self.data.get("graphic") else 0
+ table = 1 if self.data.get("table") else 0
+ alternatives = self.data.get("alternative_elements")
if graphic + table > 1 and len(alternatives) == 0:
expected = "alternatives"
@@ -278,3 +259,132 @@ def validate_alternatives(self):
advice_text=advice_text,
advice_params=advice_params,
)
+
+ def validate_tr_not_in_table(self):
+ """
+ Validates that is not a direct child of .
+ The first level of must not contain (NISO JATS model).
+
+ Returns:
+ The validation result in the expected format, or None if no exists.
+ """
+ if not self.data.get("table"):
+ return None
+ has_tr = self.data.get("has_tr_in_table")
+ is_valid = not has_tr
+ return build_response(
+ title="table structure",
+ parent={
+ "parent": self.data.get("parent"),
+ "parent_id": self.data.get("parent_id"),
+ "parent_article_type": self.data.get("parent_article_type"),
+ "parent_lang": self.data.get("parent_lang"),
+ },
+ item="table-wrap",
+ sub_item="table/tr",
+ validation_type="exist",
+ is_valid=is_valid,
+ expected=" must not be a direct child of ",
+ obtained="..." if has_tr else " not found as direct child of ",
+ advice=f'Remove as a direct child of in {self.xml}. Use or to wrap elements.',
+ data=self.data,
+ error_level=self.rules["tr_in_table_error_level"],
+ advice_text='Remove as a direct child of in {xml}. Use or to wrap elements.',
+ advice_params={"xml": self.xml},
+ )
+
+ def validate_th_in_thead(self):
+ """
+ Validates that only appears as a descendant of .
+
+ Returns:
+ The validation result in the expected format, or None if no exists.
+ """
+ if not self.data.get("table"):
+ return None
+ has_th_outside = self.data.get("has_th_outside_thead")
+ is_valid = not has_th_outside
+ return build_response(
+ title="th in thead",
+ parent={
+ "parent": self.data.get("parent"),
+ "parent_id": self.data.get("parent_id"),
+ "parent_article_type": self.data.get("parent_article_type"),
+ "parent_lang": self.data.get("parent_lang"),
+ },
+ item="table-wrap",
+ sub_item="th",
+ validation_type="exist",
+ is_valid=is_valid,
+ expected=" only as descendant of ",
+ obtained=" found outside " if has_th_outside else " only in ",
+ advice=f'Move elements to be inside in {self.xml}. Consult SPS documentation for more detail.',
+ data=self.data,
+ error_level=self.rules["th_in_thead_error_level"],
+ advice_text='Move elements to be inside in {xml}. Consult SPS documentation for more detail.',
+ advice_params={"xml": self.xml},
+ )
+
+ def validate_td_in_tbody(self):
+ """
+ Validates that only appears as a descendant of .
+
+ Returns:
+ The validation result in the expected format, or None if no exists.
+ """
+ if not self.data.get("table"):
+ return None
+ has_td_outside = self.data.get("has_td_outside_tbody")
+ is_valid = not has_td_outside
+ return build_response(
+ title="td in tbody",
+ parent={
+ "parent": self.data.get("parent"),
+ "parent_id": self.data.get("parent_id"),
+ "parent_article_type": self.data.get("parent_article_type"),
+ "parent_lang": self.data.get("parent_lang"),
+ },
+ item="table-wrap",
+ sub_item="td",
+ validation_type="exist",
+ is_valid=is_valid,
+ expected=" only as descendant of ",
+ obtained=" found outside " if has_td_outside else " only in ",
+ advice=f'Move elements to be inside in {self.xml}. Consult SPS documentation for more detail.',
+ data=self.data,
+ error_level=self.rules["td_in_tbody_error_level"],
+ advice_text='Move elements to be inside in {xml}. Consult SPS documentation for more detail.',
+ advice_params={"xml": self.xml},
+ )
+
+ def validate_tbody(self):
+ """
+ Validates the presence of in .
+
+ Returns:
+ The validation result in the expected format, or None if no exists.
+ """
+ if not self.data.get("table"):
+ return None
+ has_tbody = self.data.get("has_tbody")
+ is_valid = bool(has_tbody)
+ return build_response(
+ title="tbody",
+ parent={
+ "parent": self.data.get("parent"),
+ "parent_id": self.data.get("parent_id"),
+ "parent_article_type": self.data.get("parent_article_type"),
+ "parent_lang": self.data.get("parent_lang"),
+ },
+ item="table-wrap",
+ sub_item="tbody",
+ validation_type="exist",
+ is_valid=is_valid,
+ expected=" element in ",
+ obtained="" if has_tbody else None,
+ advice=f'Add inside in {self.xml}. Consult SPS documentation for more detail.',
+ data=self.data,
+ error_level=self.rules["tbody_error_level"],
+ advice_text='Add inside in {xml}. Consult SPS documentation for more detail.',
+ advice_params={"xml": self.xml},
+ )
diff --git a/packtools/sps/validation_rules/tablewrap_rules.json b/packtools/sps/validation_rules/tablewrap_rules.json
index da4b65f9e..9af620329 100644
--- a/packtools/sps/validation_rules/tablewrap_rules.json
+++ b/packtools/sps/validation_rules/tablewrap_rules.json
@@ -2,9 +2,14 @@
"table_wrap_rules": {
"absent_error_level": "WARNING",
"id_error_level": "CRITICAL",
+ "label_or_caption_error_level": "CRITICAL",
"label_error_level": "CRITICAL",
"caption_error_level": "CRITICAL",
"table_error_level": "CRITICAL",
- "alternatives_error_level": "CRITICAL"
+ "alternatives_error_level": "CRITICAL",
+ "tr_in_table_error_level": "ERROR",
+ "th_in_thead_error_level": "ERROR",
+ "td_in_tbody_error_level": "ERROR",
+ "tbody_error_level": "WARNING"
}
}
diff --git a/tests/sps/models/test_tablewrap.py b/tests/sps/models/test_tablewrap.py
index eb1f1b0de..7a2f76840 100644
--- a/tests/sps/models/test_tablewrap.py
+++ b/tests/sps/models/test_tablewrap.py
@@ -135,6 +135,12 @@ def test_data(self):
''
'
',
"graphic": "1980-5381-neco-28-02-579-gt02.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
}
self.assertDictEqual(self.table_wrap_obj.data, expected_data)
@@ -252,6 +258,12 @@ def test_get_article_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt02.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
{
"alternative_elements": ["graphic", "table"],
@@ -277,6 +289,12 @@ def test_get_article_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt03.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
]
@@ -318,6 +336,12 @@ def test_get_sub_article_translation_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt04.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
{
"alternative_elements": ["graphic", "table"],
@@ -343,6 +367,12 @@ def test_get_sub_article_translation_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt05.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
]
@@ -388,6 +418,12 @@ def test_get_all_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt02.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
{
"alternative_elements": ["graphic", "table"],
@@ -413,6 +449,12 @@ def test_get_all_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt03.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
{
"alternative_elements": ["graphic", "table"],
@@ -438,6 +480,12 @@ def test_get_all_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt04.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
{
"alternative_elements": ["graphic", "table"],
@@ -463,6 +511,12 @@ def test_get_all_table_wrappers(self):
'table codification'
'
',
"graphic": "1980-5381-neco-28-02-579-gt05.svg",
+ "graphic_alt_text": None,
+ "graphic_long_desc": None,
+ "has_tr_in_table": False,
+ "has_th_outside_thead": False,
+ "has_td_outside_tbody": False,
+ "has_tbody": False,
},
]
diff --git a/tests/sps/validation/test_tablewrap.py b/tests/sps/validation/test_tablewrap.py
index e63676f35..1337e9688 100644
--- a/tests/sps/validation/test_tablewrap.py
+++ b/tests/sps/validation/test_tablewrap.py
@@ -1,7 +1,7 @@
import unittest
from lxml import etree
-from packtools.sps.validation.tablewrap import ArticleTableWrapValidation
+from packtools.sps.validation.tablewrap import ArticleTableWrapValidation, TableWrapValidation
class TableWrapValidationTest(unittest.TestCase):
@@ -21,28 +21,12 @@ def test_validate_absent(self):
).validate()
)
- expected = [
- {
- "title": "table-wrap presence",
- "parent": "article",
- "parent_id": None,
- "parent_article_type": "research-article",
- "parent_lang": "pt",
- "item": "table-wrap",
- "sub_item": None,
- "validation_type": "exist",
- "response": "WARNING",
- "expected_value": " element",
- "got_value": None,
- "message": "Got None, expected element",
- "advice": "Add element to properly illustrate the content.",
- "data": None,
- }
- ]
-
- for i, item in enumerate(expected):
- with self.subTest(i):
- self.assertDictEqual(item, obtained[i])
+ self.assertEqual(1, len(obtained))
+ result = obtained[0]
+ self.assertEqual("table-wrap presence", result["title"])
+ self.assertEqual("WARNING", result["response"])
+ self.assertEqual("table-wrap", result["item"])
+ self.assertIsNone(result["data"])
def test_validate_id(self):
self.maxDiff = None
@@ -52,8 +36,8 @@ def test_validate_id(self):
""
""
"Table 1 "
- "table caption "
- ""
+ "table caption "
+ ""
" "
""
""
@@ -64,54 +48,126 @@ def test_validate_id(self):
).validate()
)
- expected = [
- {
- "title": "id",
- "parent": "article",
- "parent_id": None,
- "parent_article_type": "research-article",
- "parent_lang": "pt",
- "item": "table-wrap",
- "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": "table-wrap",
- "table_wrap_id": None,
- "label": "Table 1",
- "caption": "table caption",
- "footnotes": [],
- "alternative_elements": [],
- "table": ''
- 'table codification'
- '
',
- "graphic": None,
- "parent": "article",
- "parent_id": None,
- "parent_article_type": "research-article",
- "parent_lang": "pt",
- },
- }
- ]
-
- for i, item in enumerate(expected):
- with self.subTest(i):
- self.assertDictEqual(item, obtained[i])
-
- def test_validate_label(self):
+ # Find the id validation result
+ id_results = [r for r in obtained if r["title"] == "id"]
+ self.assertEqual(1, len(id_results))
+ result = id_results[0]
+ self.assertEqual("CRITICAL", result["response"])
+ self.assertEqual("table-wrap", result["item"])
+ self.assertEqual("id", result["sub_item"])
+ self.assertIsNone(result["got_value"])
+
+ def test_validate_id_present(self):
+ self.maxDiff = None
+ xml_tree = etree.fromstring(
+ ''
+ ""
+ ''
+ "Table 1 "
+ "table caption "
+ ""
+ " "
+ ""
+ " "
+ )
+ obtained = list(
+ ArticleTableWrapValidation(
+ xml_tree=xml_tree, rules={"id_error_level": "CRITICAL"}
+ ).validate()
+ )
+
+ id_results = [r for r in obtained if r["title"] == "id"]
+ self.assertEqual(1, len(id_results))
+ result = id_results[0]
+ self.assertEqual("OK", result["response"])
+ self.assertEqual("t01", result["got_value"])
+
+ def test_validate_label_or_caption_both_missing(self):
+ self.maxDiff = None
+ xml_tree = etree.fromstring(
+ ''
+ ""
+ ''
+ ""
+ " "
+ ""
+ " "
+ )
+ obtained = list(
+ ArticleTableWrapValidation(
+ xml_tree=xml_tree,
+ rules={"label_or_caption_error_level": "CRITICAL"},
+ ).validate()
+ )
+
+ results = [r for r in obtained if r["title"] == "label or caption"]
+ self.assertEqual(1, len(results))
+ result = results[0]
+ self.assertEqual("CRITICAL", result["response"])
+ self.assertIsNone(result["got_value"])
+
+ def test_validate_label_or_caption_only_label(self):
+ self.maxDiff = None
+ xml_tree = etree.fromstring(
+ ''
+ ""
+ ''
+ "Table 1 "
+ ""
+ " "
+ ""
+ " "
+ )
+ obtained = list(
+ ArticleTableWrapValidation(
+ xml_tree=xml_tree,
+ rules={"label_or_caption_error_level": "CRITICAL"},
+ ).validate()
+ )
+
+ results = [r for r in obtained if r["title"] == "label or caption"]
+ self.assertEqual(1, len(results))
+ result = results[0]
+ self.assertEqual("OK", result["response"])
+
+ def test_validate_label_or_caption_only_caption(self):
+ self.maxDiff = None
+ xml_tree = etree.fromstring(
+ ''
+ ""
+ ''
+ "Risk factors "
+ ""
+ " "
+ ""
+ " "
+ )
+ obtained = list(
+ ArticleTableWrapValidation(
+ xml_tree=xml_tree,
+ rules={"label_or_caption_error_level": "CRITICAL"},
+ ).validate()
+ )
+
+ results = [r for r in obtained if r["title"] == "label or caption"]
+ self.assertEqual(1, len(results))
+ result = results[0]
+ self.assertEqual("OK", result["response"])
+
+ def test_validate_label_or_caption_both_present(self):
self.maxDiff = None
xml_tree = etree.fromstring(
''
""
''
- "table caption "
- ""
+ "Table 1 "
+ "Risk factors "
+ ""
" "
""
" "
@@ -119,52 +175,16 @@ def test_validate_label(self):
obtained = list(
ArticleTableWrapValidation(
xml_tree=xml_tree,
- rules={
- "label_error_level": "CRITICAL",
- },
+ rules={"label_or_caption_error_level": "CRITICAL"},
).validate()
)
- expected = [
- {
- "title": "label",
- "parent": "article",
- "parent_id": None,
- "parent_article_type": "research-article",
- "parent_lang": "pt",
- "item": "table-wrap",
- "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": "table-wrap",
- "table_wrap_id": "t01",
- "label": None,
- "caption": "table caption",
- "footnotes": [],
- "alternative_elements": [],
- "table": ''
- 'table codification'
- '
',
- "graphic": None,
- "parent": "article",
- "parent_id": None,
- "parent_article_type": "research-article",
- "parent_lang": "pt",
- },
- }
- ]
-
- for i, item in enumerate(expected):
- with self.subTest(i):
- self.assertDictEqual(item, obtained[i])
-
- def test_validate_caption(self):
+ results = [r for r in obtained if r["title"] == "label or caption"]
+ self.assertEqual(1, len(results))
+ result = results[0]
+ self.assertEqual("OK", result["response"])
+
+ def test_validate_table_missing(self):
self.maxDiff = None
xml_tree = etree.fromstring(
'"
''
"Table 1 "
- ""
+ "Table caption "
" "
"
"
''
"Table 1 "
- "Table caption "
- ""
+ "Table caption "
+ ""
'
'
" "
""
''
"Table 1 "
- "Table caption "
+ "Table caption "
""
- ""
+ ""
" "
" "
"