From 2402899c1ed7dc9c27b3b13ab9a1086bf1fb8a0f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 19:15:53 +0000
Subject: [PATCH 1/3] Initial plan
From 730a8eacae9a917280e9062552b48c0886ef9025 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 19:20:41 +0000
Subject: [PATCH 2/3] Add xml_fixer module with
fix_inline_graphic_in_caption_and_label function and comprehensive tests
Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
---
packtools/sps/utils/xml_fixer.py | 159 ++++++++++++++++
tests/sps/utils/test_xml_fixer.py | 298 ++++++++++++++++++++++++++++++
2 files changed, 457 insertions(+)
create mode 100644 packtools/sps/utils/xml_fixer.py
create mode 100644 tests/sps/utils/test_xml_fixer.py
diff --git a/packtools/sps/utils/xml_fixer.py b/packtools/sps/utils/xml_fixer.py
new file mode 100644
index 000000000..5aa02adcb
--- /dev/null
+++ b/packtools/sps/utils/xml_fixer.py
@@ -0,0 +1,159 @@
+"""
+Módulo para funções de correção de XML.
+
+Este módulo contém funções utilitárias para corrigir problemas
+comuns em documentos XML, especialmente para conformidade com SPS.
+"""
+import logging
+from lxml import etree
+from typing import List, Dict
+
+logger = logging.getLogger(__name__)
+
+
+def fix_inline_graphic_in_caption_and_label(xmltree):
+ """
+ Corrige elementos inline-graphic incorretamente posicionados dentro de caption ou label.
+
+ Esta função identifica todos os elementos `inline-graphic` que estejam dentro de
+ `label` ou `caption` e, quando não existir um elemento `graphic` no pai (fig,
+ table-wrap, boxed-text, etc), move o `inline-graphic` para fora do `label`/`caption`
+ e o renomeia para `graphic`.
+
+ Args:
+ xmltree: Objeto lxml.etree representando o documento XML a ser corrigido.
+
+ Returns:
+ list: Lista de dicionários documentando cada modificação realizada. Cada
+ dicionário contém os campos:
+ - xpath: caminho XPath do elemento modificado
+ - action: tipo de ação realizada
+ - old_parent: tag do elemento pai original
+ - new_parent: tag do novo elemento pai
+
+ Examples:
+ >>> from lxml import etree
+ >>> xml = '''
+ ...
+ ...
Título
+ ... '''
+ >>> tree = etree.fromstring(xml)
+ >>> changes = fix_inline_graphic_in_caption_and_label(tree)
+ >>> len(changes)
+ 1
+ >>> changes[0]['action']
+ 'moved_and_renamed'
+ """
+ changes = []
+
+ # Encontra todos os inline-graphic dentro de caption ou label
+ # XPath para encontrar inline-graphic em qualquer descendente de caption ou label
+ inline_graphics_in_caption_or_label = xmltree.xpath(
+ ".//caption//inline-graphic | .//label//inline-graphic"
+ )
+
+ for inline_graphic in inline_graphics_in_caption_or_label:
+ # Obtém o elemento pai do inline-graphic para documentação
+ old_parent = inline_graphic.getparent()
+ old_parent_tag = old_parent.tag if old_parent is not None else None
+
+ # Navega até encontrar o elemento container (fig, table-wrap, boxed-text, etc)
+ # que é o pai do caption/label
+ container = old_parent
+ while container is not None:
+ parent = container.getparent()
+ if parent is not None:
+ # Verifica se o pai contém caption ou label
+ has_caption_or_label = (
+ parent.find(".//caption") is not None or
+ parent.find(".//label") is not None
+ )
+ if has_caption_or_label:
+ # Este é provavelmente o container (fig, table-wrap, etc)
+ container = parent
+ break
+ container = parent
+
+ # Se não encontrou um container adequado, tenta encontrar o pai direto de caption/label
+ if container is None or container.tag in ('caption', 'label', 'title', 'p'):
+ # Procura pelo ancestral que contém caption ou label
+ current = inline_graphic
+ while current is not None:
+ parent = current.getparent()
+ if parent is None:
+ break
+ # Verifica se este elemento é um possível container
+ if parent.tag in ('fig', 'table-wrap', 'boxed-text', 'disp-formula',
+ 'supplementary-material', 'fig-group', 'article',
+ 'sub-article', 'body', 'sec', 'app'):
+ container = parent
+ break
+ current = parent
+
+ # Se ainda não encontrou container, usa o elemento raiz
+ if container is None:
+ container = xmltree
+
+ # Verifica se já existe um elemento graphic no container
+ existing_graphic = container.find(".//graphic")
+
+ if existing_graphic is None:
+ # Não existe graphic, então vamos mover e renomear o inline-graphic
+
+ # Captura o XPath antes de modificar (para documentação)
+ try:
+ xpath = xmltree.getpath(inline_graphic)
+ except AttributeError:
+ # Fallback se getpath não estiver disponível
+ xpath = f".//{inline_graphic.tag}"
+
+ # Remove o inline-graphic de sua posição atual
+ old_parent.remove(inline_graphic)
+
+ # Cria um novo elemento graphic com os mesmos atributos
+ graphic = etree.Element("graphic", attrib=inline_graphic.attrib)
+
+ # Copia o texto e tail se existirem
+ graphic.text = inline_graphic.text
+ graphic.tail = inline_graphic.tail
+
+ # Copia todos os sub-elementos, se houver
+ for child in inline_graphic:
+ graphic.append(child)
+
+ # Adiciona o novo elemento graphic ao container
+ # Tenta adicionar após caption/label, se existirem
+ caption = container.find(".//caption")
+ label = container.find(".//label")
+
+ inserted = False
+ if caption is not None:
+ # Adiciona após o caption
+ caption_index = list(container).index(caption)
+ container.insert(caption_index + 1, graphic)
+ inserted = True
+ elif label is not None:
+ # Adiciona após o label
+ label_index = list(container).index(label)
+ container.insert(label_index + 1, graphic)
+ inserted = True
+
+ if not inserted:
+ # Adiciona como último filho
+ container.append(graphic)
+
+ # Registra a mudança
+ change = {
+ "xpath": xpath,
+ "action": "moved_and_renamed",
+ "old_parent": old_parent_tag,
+ "new_parent": container.tag,
+ }
+ changes.append(change)
+
+ logger.info(
+ f"Moved and renamed inline-graphic to graphic: {xpath} "
+ f"from {old_parent_tag} to {container.tag}"
+ )
+
+ return changes
diff --git a/tests/sps/utils/test_xml_fixer.py b/tests/sps/utils/test_xml_fixer.py
new file mode 100644
index 000000000..d264cbf5d
--- /dev/null
+++ b/tests/sps/utils/test_xml_fixer.py
@@ -0,0 +1,298 @@
+# coding: utf-8
+import unittest
+from lxml import etree
+
+from packtools.sps.utils import xml_fixer
+
+
+class TestFixInlineGraphicInCaptionAndLabel(unittest.TestCase):
+ """Testes para a função fix_inline_graphic_in_caption_and_label."""
+
+ def test_simple_inline_graphic_in_caption_title(self):
+ """Testa correção de inline-graphic simples dentro de caption/title."""
+ xml = etree.fromstring(
+ b"""
+
+
+ T\xc3\xadtulo da figura
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve uma mudança
+ self.assertEqual(len(changes), 1)
+ self.assertEqual(changes[0]['action'], 'moved_and_renamed')
+ self.assertEqual(changes[0]['old_parent'], 'title')
+ self.assertEqual(changes[0]['new_parent'], 'fig')
+
+ # Verifica que inline-graphic foi removido de caption
+ inline_graphics_in_caption = xml.xpath(".//caption//inline-graphic")
+ self.assertEqual(len(inline_graphics_in_caption), 0)
+
+ # Verifica que graphic foi adicionado ao fig
+ graphics = xml.findall(".//graphic")
+ self.assertEqual(len(graphics), 1)
+
+ # Verifica que o atributo foi preservado
+ graphic = graphics[0]
+ self.assertEqual(
+ graphic.get("{http://www.w3.org/1999/xlink}href"),
+ "img1.jpg"
+ )
+
+ def test_inline_graphic_in_label(self):
+ """Testa correção de inline-graphic dentro de label."""
+ xml = etree.fromstring(
+ b"""
+
+
T\xc3\xadtulo
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve uma mudança
+ self.assertEqual(len(changes), 1)
+ self.assertEqual(changes[0]['action'], 'moved_and_renamed')
+ self.assertEqual(changes[0]['old_parent'], 'label')
+ self.assertEqual(changes[0]['new_parent'], 'fig')
+
+ # Verifica que inline-graphic foi removido de label
+ inline_graphics_in_label = xml.xpath(".//label//inline-graphic")
+ self.assertEqual(len(inline_graphics_in_label), 0)
+
+ # Verifica que graphic foi adicionado
+ graphics = xml.findall(".//graphic")
+ self.assertEqual(len(graphics), 1)
+
+ def test_multiple_inline_graphics(self):
+ """Testa correção de múltiplos inline-graphics."""
+ xml = etree.fromstring(
+ b"""
+
+
+
+
+ T\xc3\xadtulo 1
+
+
+
+
+
+ T\xc3\xadtulo 2
+
+
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve duas mudanças
+ self.assertEqual(len(changes), 2)
+
+ # Verifica que não há mais inline-graphics em caption
+ inline_graphics_in_caption = xml.xpath(".//caption//inline-graphic")
+ self.assertEqual(len(inline_graphics_in_caption), 0)
+
+ # Verifica que foram criados dois graphics
+ graphics = xml.findall(".//graphic")
+ self.assertEqual(len(graphics), 2)
+
+ # Verifica que estão nos lugares corretos
+ fig_graphic = xml.find(".//fig[@id='f1']/graphic")
+ self.assertIsNotNone(fig_graphic)
+
+ table_graphic = xml.find(".//table-wrap[@id='t1']/graphic")
+ self.assertIsNotNone(table_graphic)
+
+ def test_graphic_already_exists(self):
+ """Testa que não modifica quando graphic já existe no pai."""
+ xml = etree.fromstring(
+ b"""
+
+
+ T\xc3\xadtulo
+
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que não houve mudanças
+ self.assertEqual(len(changes), 0)
+
+ # Verifica que inline-graphic ainda está em caption
+ inline_graphics_in_caption = xml.xpath(".//caption//inline-graphic")
+ self.assertEqual(len(inline_graphics_in_caption), 1)
+
+ # Verifica que graphic original permanece
+ graphics = xml.findall(".//graphic")
+ self.assertEqual(len(graphics), 1)
+ self.assertEqual(
+ graphics[0].get("{http://www.w3.org/1999/xlink}href"),
+ "img_correct.jpg"
+ )
+
+ def test_table_wrap_context(self):
+ """Testa correção em contexto de table-wrap."""
+ xml = etree.fromstring(
+ b"""
+
+
+ T\xc3\xadtulo da tabela
+
+
+
+
Dados
+
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve uma mudança
+ self.assertEqual(len(changes), 1)
+ self.assertEqual(changes[0]['new_parent'], 'table-wrap')
+
+ # Verifica que graphic foi adicionado a table-wrap
+ graphics = xml.findall(".//graphic")
+ self.assertEqual(len(graphics), 1)
+
+ def test_boxed_text_context(self):
+ """Testa correção em contexto de boxed-text."""
+ xml = etree.fromstring(
+ b"""
+
+
+ T\xc3\xadtulo do box
+
+
Conte\xc3\xbado do box
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve uma mudança
+ self.assertEqual(len(changes), 1)
+ self.assertEqual(changes[0]['new_parent'], 'boxed-text')
+
+ # Verifica que graphic foi adicionado
+ graphics = xml.findall(".//graphic")
+ self.assertEqual(len(graphics), 1)
+
+ def test_preserves_all_attributes(self):
+ """Testa que todos os atributos são preservados."""
+ xml = etree.fromstring(
+ b"""
+
+
+ T\xc3\xadtulo
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve uma mudança
+ self.assertEqual(len(changes), 1)
+
+ # Verifica que todos os atributos foram preservados
+ graphic = xml.find(".//graphic")
+ self.assertIsNotNone(graphic)
+ self.assertEqual(graphic.get("id"), "ig1")
+ self.assertEqual(
+ graphic.get("{http://www.w3.org/1999/xlink}href"),
+ "img.jpg"
+ )
+ self.assertEqual(graphic.get("content-type"), "image/jpeg")
+
+ def test_preserves_namespaces(self):
+ """Testa que namespaces são preservados."""
+ xml = etree.fromstring(
+ b"""
+
+
+ T\xc3\xadtulo
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve uma mudança
+ self.assertEqual(len(changes), 1)
+
+ # Verifica que namespace foi preservado
+ graphic = xml.find(".//graphic")
+ self.assertIsNotNone(graphic)
+ self.assertEqual(
+ graphic.get("{http://www.w3.org/1999/xlink}href"),
+ "img.jpg"
+ )
+
+ def test_caption_without_title(self):
+ """Testa correção quando inline-graphic está diretamente em caption sem title."""
+ xml = etree.fromstring(
+ b"""
+
+
+
Par\xc3\xa1grafo
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que houve uma mudança
+ self.assertEqual(len(changes), 1)
+
+ # Verifica que graphic foi adicionado
+ graphics = xml.findall(".//graphic")
+ self.assertEqual(len(graphics), 1)
+
+ def test_no_inline_graphics(self):
+ """Testa que retorna lista vazia quando não há inline-graphics para corrigir."""
+ xml = etree.fromstring(
+ b"""
+
+
T\xc3\xadtulo
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que não houve mudanças
+ self.assertEqual(len(changes), 0)
+
+ def test_inline_graphic_outside_caption_label_not_affected(self):
+ """Testa que inline-graphics fora de caption/label não são afetados."""
+ xml = etree.fromstring(
+ b"""
+
+
+
+
T\xc3\xadtulo
+
+
Par\xc3\xa1grafo com inline
+
+ """
+ )
+
+ changes = xml_fixer.fix_inline_graphic_in_caption_and_label(xml)
+
+ # Verifica que não houve mudanças
+ self.assertEqual(len(changes), 0)
+
+ # Verifica que inline-graphic ainda está no parágrafo
+ inline_graphics = xml.xpath(".//p//inline-graphic")
+ self.assertEqual(len(inline_graphics), 1)
+
+
+if __name__ == "__main__":
+ unittest.main()
From 99d8b8f0acdeead885028675dff8db4a7fff51b9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 7 Oct 2025 19:22:34 +0000
Subject: [PATCH 3/3] Add type hints and improve imports in xml_fixer module
Co-authored-by: robertatakenaka <505143+robertatakenaka@users.noreply.github.com>
---
packtools/sps/utils/xml_fixer.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packtools/sps/utils/xml_fixer.py b/packtools/sps/utils/xml_fixer.py
index 5aa02adcb..7850ccd8e 100644
--- a/packtools/sps/utils/xml_fixer.py
+++ b/packtools/sps/utils/xml_fixer.py
@@ -5,13 +5,14 @@
comuns em documentos XML, especialmente para conformidade com SPS.
"""
import logging
-from lxml import etree
from typing import List, Dict
+from lxml import etree
+
logger = logging.getLogger(__name__)
-def fix_inline_graphic_in_caption_and_label(xmltree):
+def fix_inline_graphic_in_caption_and_label(xmltree: etree._Element) -> List[Dict[str, str]]:
"""
Corrige elementos inline-graphic incorretamente posicionados dentro de caption ou label.