Skip to content
35 changes: 33 additions & 2 deletions packtools/sps/models/fig.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,35 @@ def file_extension(self):
return file_name.split(".")[-1]
return None

@property
def graphic_alt_text(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rossi-Luciano talvez já esteja resolvido para todos os graphic não importa o contexto. Antes de concluir que tem que criar novas validações, verifique se já existem

"""
Extracts alt-text from graphic within alternatives.

Returns:
str or None: The text content of <alt-text> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/graphic")
if graphic is not None:
alt_text_elem = graphic.find("alt-text")
if alt_text_elem is not None:
return alt_text_elem.text
return None

@property
def graphic_long_desc(self):
"""
Extracts long-desc from graphic within alternatives.

Returns:
str or None: The text content of <long-desc> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/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
Comment on lines +65 to +90
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphic_alt_text/graphic_long_desc return only the .text of the first <alternatives/graphic>/ or . This misses cases where the element exists but is empty ( => .text is None) and cases with multiple inside . Consider returning an explicit presence indicator (or a list of found nodes/texts) so validations can reliably fail when these elements are present, even if empty.

Suggested change
Extracts alt-text from graphic within alternatives.
Returns:
str or None: The text content of <alt-text> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/graphic")
if graphic is not None:
alt_text_elem = graphic.find("alt-text")
if alt_text_elem is not None:
return alt_text_elem.text
return None
@property
def graphic_long_desc(self):
"""
Extracts long-desc from graphic within alternatives.
Returns:
str or None: The text content of <long-desc> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/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
Extracts alt-text from one or more graphics within alternatives.
Returns:
str or None: The concatenated text content of all <alt-text> elements
found under <alternatives>/<graphic>. Returns None if no <alt-text>
elements are present at all (even if <graphic> exists). If at least
one <alt-text> element is present but empty, it contributes an empty
string to the result.
"""
graphics = self.element.findall(".//alternatives/graphic")
alt_texts = []
for graphic in graphics:
for alt_text_elem in graphic.findall("alt-text"):
# Normalize None to empty string so presence is distinguishable
# from complete absence of <alt-text> elements.
alt_texts.append(alt_text_elem.text or "")
if not alt_texts:
return None
# Join multiple alt-text values with a space to preserve the original
# return type (str) while supporting multiple graphics.
return " ".join(alt_texts)
@property
def graphic_long_desc(self):
"""
Extracts long-desc from one or more graphics within alternatives.
Returns:
str or None: The concatenated text content of all <long-desc> elements
found under <alternatives>/<graphic>. Returns None if no <long-desc>
elements are present at all (even if <graphic> exists). If at least
one <long-desc> element is present but empty, it contributes an empty
string to the result.
"""
graphics = self.element.findall(".//alternatives/graphic")
long_desc_texts = []
for graphic in graphics:
for long_desc_elem in graphic.findall("long-desc"):
# Normalize None to empty string so presence is distinguishable
# from complete absence of <long-desc> elements.
long_desc_texts.append(long_desc_elem.text or "")
if not long_desc_texts:
return None
# Join multiple long-desc values with a space to preserve the original
# return type (str) while supporting multiple graphics.
return " ".join(long_desc_texts)

Copilot uses AI. Check for mistakes.

@property
def data(self):
Expand All @@ -70,8 +99,10 @@ def data(self):
"graphic": self.graphic_href,
"caption": self.caption_text,
"source_attrib": self.source_attrib,
"alternatives": self.alternative_elements,
"file_extension": self.file_extension
"alternative_elements": self.alternative_elements,
"file_extension": self.file_extension,
"graphic_alt_text": self.graphic_alt_text,
"graphic_long_desc": self.graphic_long_desc,
Comment on lines 101 to +105
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Fig.data(), the key for alternatives was changed from "alternatives" to "alternative_elements". This is a breaking change for existing consumers/tests (e.g., packtools/sps/validation/fig.py uses data.get("alternatives")), and will cause figure validations to treat valid figures as missing alternatives. Either keep the original "alternatives" key (and optionally add "alternative_elements" as an alias) or update all downstream references and related tests in the same PR.

Copilot uses AI. Check for mistakes.
}


Expand Down
39 changes: 36 additions & 3 deletions packtools/sps/models/formula.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def mml_math(self):
if formula is not None:
return ET.tostring(formula, encoding="unicode", method="text").strip()


@property
def tex_math(self):
"""
Expand Down Expand Up @@ -95,6 +94,36 @@ def graphic(self):
if formula is not None and formula.get(f"{namespace}href") is not None
]

@property
def graphic_alt_text(self):
"""
Extracts alt-text from graphic within alternatives.

Returns:
str or None: The text content of <alt-text> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/graphic")
if graphic is not None:
alt_text_elem = graphic.find("alt-text")
if alt_text_elem is not None:
return alt_text_elem.text
Comment on lines +105 to +109
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphic_alt_text currently returns alt_text_elem.text from only the first <alternatives/graphic>. This will miss cases where exists but is empty () or where contains multiple children. Consider returning an explicit presence indicator (or list) instead of relying on .text so validations can reliably fail when the element exists.

Copilot uses AI. Check for mistakes.
return None

@property
def graphic_long_desc(self):
"""
Extracts long-desc from graphic within alternatives.

Returns:
str or None: The text content of <long-desc> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/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

Comment on lines +115 to +126
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphic_long_desc currently returns long_desc_elem.text from only the first <alternatives/graphic>. This will miss cases where exists but is empty () or where contains multiple children. Consider returning an explicit presence indicator (or list) instead of relying on .text so validations can reliably fail when the element exists.

Suggested change
Extracts long-desc from graphic within alternatives.
Returns:
str or None: The text content of <long-desc> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/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
Extracts long-desc from graphics within alternatives.
Returns:
str or None: The text content of <long-desc> if present. If multiple
<long-desc> elements exist under <alternatives>/<graphic>, their texts
are concatenated with spaces. Returns None only when no <long-desc>
elements are present at all.
"""
long_desc_elems = self.element.findall(".//alternatives/graphic/long-desc")
if not long_desc_elems:
return None
texts = [(elem.text or "").strip() for elem in long_desc_elems]
# Filter out purely empty strings only if there are non-empty ones,
# so empty <long-desc/> still counts when it is the only content.
non_empty_texts = [t for t in texts if t]
if non_empty_texts:
texts_to_use = non_empty_texts
else:
texts_to_use = texts
if len(texts_to_use) == 1:
return texts_to_use[0]
return " ".join(texts_to_use)

Copilot uses AI. Check for mistakes.
@property
def data(self):
"""
Expand All @@ -109,6 +138,8 @@ def data(self):
- 'mml_math' (str or None): The MathML content, if available.
- 'tex_math' (str or None): The TeX math content, if available.
- 'graphic' (list): A list of hrefs from graphic elements.
- 'graphic_alt_text' (str or None): The alt-text from graphic in alternatives.
- 'graphic_long_desc' (str or None): The long-desc from graphic in alternatives.
"""
alternative_parent = self.element.tag # 'disp-formula' or 'inline-formula'
return {
Expand All @@ -118,7 +149,9 @@ def data(self):
"alternative_elements": self.alternative_elements,
"mml_math": self.mml_math,
"tex_math": self.tex_math,
"graphic": self.graphic
"graphic": self.graphic,
"graphic_alt_text": self.graphic_alt_text,
"graphic_long_desc": self.graphic_long_desc,
}


Expand Down Expand Up @@ -158,7 +191,7 @@ def disp_formula_items(self):
@property
def inline_formula_items(self):
"""
Generator that yields formulas with their respective parent context.
Generator that yields inline formulas with their respective parent context.

Yields:
dict: A dictionary containing the formula data along with its parent context,
Expand Down
32 changes: 32 additions & 0 deletions packtools/sps/models/tablewrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,36 @@ def graphic(self):
return graphic.get("{http://www.w3.org/1999/xlink}href")
return None

@property
def graphic_alt_text(self):
"""
Extracts alt-text from graphic within alternatives.

Returns:
str or None: The text content of <alt-text> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/graphic")
if graphic is not None:
alt_text_elem = graphic.find("alt-text")
if alt_text_elem is not None:
return alt_text_elem.text
Comment on lines +82 to +91
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphic_alt_text currently returns alt_text_elem.text from only the first <alternatives/graphic>. This will miss prohibited that is empty () or appears under a second/third inside . Consider collecting all matching alt-text nodes (or returning a boolean/count) so validations can reliably detect presence.

Suggested change
Extracts alt-text from graphic within alternatives.
Returns:
str or None: The text content of <alt-text> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/graphic")
if graphic is not None:
alt_text_elem = graphic.find("alt-text")
if alt_text_elem is not None:
return alt_text_elem.text
Extracts alt-text from graphics within alternatives.
Returns:
str or None: The text content of the first <alt-text> found under any
<graphic> inside <alternatives>. Returns an empty string ("") if an
<alt-text> element is present but has no text content, and None if no
<alt-text> element is found at all.
"""
graphics = self.element.findall(".//alternatives/graphic")
for graphic in graphics:
alt_text_elem = graphic.find("alt-text")
if alt_text_elem is not None:
# Normalize empty alt-text (e.g., <alt-text/>) to empty string
return alt_text_elem.text or ""

Copilot uses AI. Check for mistakes.
return None

@property
def graphic_long_desc(self):
"""
Extracts long-desc from graphic within alternatives.

Returns:
str or None: The text content of <long-desc> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/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

Comment on lines +97 to +108
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

graphic_long_desc currently returns long_desc_elem.text from only the first <alternatives/graphic>. This will miss prohibited that is empty () or appears under another within . Consider collecting all matching long-desc nodes (or returning a boolean/count) so validations can reliably detect presence.

Suggested change
Extracts long-desc from graphic within alternatives.
Returns:
str or None: The text content of <long-desc> if present, None otherwise.
"""
graphic = self.element.find(".//alternatives/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
Extracts long-desc from graphics within alternatives.
Returns:
str or None: The concatenated text content of all <long-desc> elements
under <alternatives>/<graphic>. Returns None if no <long-desc> elements
are present at all (even if some are empty).
"""
long_desc_elems = self.element.findall(".//alternatives/graphic/long-desc")
if not long_desc_elems:
return None
# Combine text from all long-desc elements, treating missing text as empty
parts = []
for elem in long_desc_elems:
text = elem.text or ""
# Normalize whitespace around each piece without erasing the distinction
# between absence of elements (None) and empty content ("").
text = text.strip()
if text:
parts.append(text)
# If there were long-desc elements but all were empty/whitespace-only,
# return an empty string to signal presence without content.
if not parts:
return ""
return " ".join(parts)

Copilot uses AI. Check for mistakes.
@property
def data(self):
return {
Expand All @@ -87,6 +117,8 @@ def data(self):
"alternative_elements": self.alternative_elements,
"table": self.table,
"graphic": self.graphic,
"graphic_alt_text": self.graphic_alt_text,
"graphic_long_desc": self.graphic_long_desc,
}


Expand Down
Loading
Loading