Skip to content

Commit 1508c58

Browse files
Fix xml with pre year and absent elements (#1048)
* Aplica black * fix: adiciona tratamento para TypeError em propriedades de datas - Trata TypeError além de KeyError em article_year e collection_year - Previne erros quando article_date ou collection_date são None * feat: adiciona normalização de valores zero em campos de paginação e issue - Implementa função zero_to_none() para normalizar campos numéricos - Remove zeros não significativos de volume, number, fpage e lpage - Melhora lógica de order com fallback para últimos 5 dígitos do pid v2 - Adiciona tratamento robusto quando order não está disponível * feat: implementa geração de PID v2 e métodos auxiliares - Adiciona generated_pid_v2() para criar PIDs programaticamente - Implementa get_article_pid_suffix() e generate_issue_pid_suffix() - Adiciona lógica para gerar order baseado em supplement e number - Cria funções auxiliares: string_to_5_digits() usando CRC32 e extract_number() - Melhora alternative_sps_pkg_name_suffix usando order como fallback - Corrige comparação de fpage zero (!= ao invés de 'not ==')
1 parent 7d018fa commit 1508c58

File tree

5 files changed

+205
-65
lines changed

5 files changed

+205
-65
lines changed

packtools/sps/pid_provider/models/dates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,14 @@ def article_date_isoformat(self):
125125
def article_year(self):
126126
try:
127127
return self.article_date["year"]
128-
except KeyError:
128+
except (TypeError, KeyError):
129129
return None
130130

131131
@cached_property
132132
def collection_year(self):
133133
try:
134134
return self.collection_date["year"]
135-
except KeyError:
135+
except (TypeError, KeyError):
136136
return None
137137

138138
@cached_property

packtools/sps/pid_provider/models/front_articlemeta_issue.py

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
"""<article>
2+
<front>
3+
<article-meta>
4+
<pub-date publication-format="electronic" date-type="collection">
5+
<year>2003</year>
6+
</pub-date>
7+
<volume>4</volume>
8+
<issue>1</issue>
9+
<fpage>108</fpage>
10+
<lpage>123</lpage>
11+
</article-meta>
12+
</front>
13+
</article>
14+
"""
15+
116
"""<article>
217
<front>
318
<article-meta>
@@ -30,10 +45,7 @@ def _extract_number_and_supplment_from_issue_element(issue):
3045
issue = issue.strip().replace(".", "")
3146
splitted = [s for s in issue.split() if s]
3247

33-
splitted = ["spe"
34-
if "spe" in s.lower() and s.isalpha() else s
35-
for s in splitted
36-
]
48+
splitted = ["spe" if "spe" in s.lower() and s.isalpha() else s for s in splitted]
3749
if len(splitted) == 1:
3850
issue = splitted[0]
3951
if issue.isdigit():
@@ -62,6 +74,31 @@ def _extract_number_and_supplment_from_issue_element(issue):
6274
return "".join(splitted), None
6375

6476

77+
def zero_to_none(value):
78+
"""
79+
Normaliza valores de campos numéricos de paginação e volume/número,
80+
removendo zeros não significativos.
81+
82+
Usado para: volume, number, fpage, lpage
83+
84+
Args:
85+
value: Valor a ser normalizado (string ou None)
86+
87+
Returns:
88+
String normalizada ou None se o valor for vazio ou zero
89+
"""
90+
if not value:
91+
return None
92+
93+
try:
94+
if int(value) == 0:
95+
return None
96+
return value
97+
except (TypeError, ValueError):
98+
# Valor não é numérico, retorna como está
99+
return value
100+
101+
65102
class ArticleMetaIssue:
66103

67104
def __init__(self, xmltree):
@@ -70,8 +107,12 @@ def __init__(self, xmltree):
70107
@property
71108
def data(self):
72109
attr_names = (
73-
"volume", "number", "suppl",
74-
"fpage", "fpage_seq", "lpage",
110+
"volume",
111+
"number",
112+
"suppl",
113+
"fpage",
114+
"fpage_seq",
115+
"lpage",
75116
"elocation_id",
76117
)
77118
_data = {}
@@ -96,7 +137,8 @@ def collection_date(self):
96137

97138
@property
98139
def volume(self):
99-
return self.xmltree.findtext(".//front/article-meta/volume")
140+
volume = self.xmltree.findtext(".//front/article-meta/volume")
141+
return zero_to_none(volume)
100142

101143
@property
102144
def issue(self):
@@ -107,7 +149,7 @@ def number(self):
107149
_issue = self.issue
108150
if _issue:
109151
n, s = _extract_number_and_supplment_from_issue_element(_issue)
110-
return n
152+
return zero_to_none(n)
111153

112154
@property
113155
def suppl(self):
@@ -126,7 +168,8 @@ def elocation_id(self):
126168

127169
@property
128170
def fpage(self):
129-
return self.xmltree.findtext(".//front/article-meta/fpage")
171+
fpage = self.xmltree.findtext(".//front/article-meta/fpage")
172+
return zero_to_none(fpage)
130173

131174
@property
132175
def fpage_seq(self):
@@ -137,11 +180,24 @@ def fpage_seq(self):
137180

138181
@property
139182
def lpage(self):
140-
return self.xmltree.findtext(".//front/article-meta/lpage")
183+
lpage = self.xmltree.findtext(".//front/article-meta/lpage")
184+
return zero_to_none(lpage)
141185

142186
@property
143187
def order(self):
188+
"""
189+
Obtém o order do artigo, primeiro tentando article-id[@pub-id-type="other"],
190+
depois usando os últimos 5 dígitos do pid v2 como fallback.
191+
192+
Returns:
193+
int: Order do artigo ou 0 se não for possível obter um valor válido
194+
"""
144195
_order = self.xmltree.findtext('.//article-id[@pub-id-type="other"]')
145-
if _order is None:
196+
197+
if not _order:
198+
# Fallback: usa os últimos 5 dígitos do pid v2
146199
_order = ArticleIds(self.xmltree).v2
147-
return int(_order)
200+
if _order:
201+
_order = _order[-5:]
202+
203+
return int(_order or 0)

packtools/sps/pid_provider/xml_loader.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
def load_xml(xml):
1010
"""
1111
Carrega e processa XML, corrigindo entidades na entrada.
12-
12+
1313
Análise:
1414
- sucesso
1515
- Exemplo de saída:
@@ -34,14 +34,14 @@ def load_xml(xml):
3434
</article>
3535
"""
3636
return etree.tostring(
37-
etree.fromstring(fix_pre_loading(xml)),
38-
method="xml", encoding="utf-8").decode("utf-8")
37+
etree.fromstring(fix_pre_loading(xml)), method="xml", encoding="utf-8"
38+
).decode("utf-8")
3939

4040

4141
def fix_entities(xml):
4242
"""
4343
Corrige entidades usando parser HTML e formatação de saída.
44-
44+
4545
Análise:
4646
- Usa html_parser_ent2char internamente
4747
- Aplica format_output para corrigir entidades finais
@@ -54,7 +54,7 @@ def fix_entities(xml):
5454
def xml_parser_ent2char(xml):
5555
"""
5656
Usa parser XML do lxml com modo recover para processar entidades.
57-
57+
5858
Análise:
5959
- PERDE OS CARACTERES
6060
- Remove completamente as entidades não reconhecidas
@@ -78,7 +78,7 @@ def xml_parser_ent2char(xml):
7878
</content>
7979
</body>
8080
</article>
81-
81+
8282
Problema: Entidades como &rsquo;, &ldquo;, &mdash; são completamente removidas
8383
ao invés de convertidas para seus caracteres correspondentes.
8484
"""
@@ -94,14 +94,14 @@ def xml_parser_ent2char(xml):
9494
def html_unescape_ent2char(xml):
9595
"""
9696
Usa html.unescape para converter entidades HTML.
97-
97+
9898
Análise:
9999
- NÃO CONSEGUE LER O XML
100100
- Falha com erro: Entity 'lquo' not defined
101101
- Exemplo de erro:
102102
ERROR:root:Entity 'lquo' not defined, line 5, column 38
103103
lxml.etree.XMLSyntaxError: Entity 'lquo' not defined
104-
104+
105105
Problema: html.unescape converte as entidades, mas o XML resultante
106106
não é válido porque algumas entidades HTML não são reconhecidas
107107
pelo parser XML padrão.
@@ -118,7 +118,7 @@ def html_unescape_ent2char(xml):
118118
def html_parser_ent2char(xml):
119119
"""
120120
Usa parser HTML do lxml para processar entidades.
121-
121+
122122
Análise:
123123
- PERDE O ARTICLE/BODY, MAS PERDE O ; APÓS LQUO E RQUO
124124
- Converte a maioria das entidades corretamente
@@ -140,7 +140,7 @@ def html_parser_ent2char(xml):
140140
<p>187 : »</p>
141141
</content>
142142
</article>
143-
143+
144144
Problemas:
145145
1. Parser HTML adiciona estrutura <html><body> que precisa ser removida
146146
2. Entidades &lquo; e &rquo; perdem o ponto-e-vírgula final
@@ -149,7 +149,9 @@ def html_parser_ent2char(xml):
149149
try:
150150
parser = etree.HTMLParser()
151151
root = etree.fromstring(xml, parser)
152-
return etree.tostring(root.find(".").find("body").find("*"), method="xml", encoding="utf-8").decode("utf-8")
152+
return etree.tostring(
153+
root.find(".").find("body").find("*"), method="xml", encoding="utf-8"
154+
).decode("utf-8")
153155
except Exception as e:
154156
logging.info("opção 3")
155157
logging.exception(e)
@@ -158,24 +160,24 @@ def html_parser_ent2char(xml):
158160
def bs_ent2char_(xml):
159161
"""
160162
Testa diferentes parsers do BeautifulSoup.
161-
163+
162164
Análises por parser:
163-
165+
164166
1. "xml" (Alias para lxml-xml):
165167
- PERDE OS CARACTERES
166168
- Similar ao xml_parser_ent2char
167-
169+
168170
2. "lxml" (Parser HTML com lxml):
169171
- PERDE O ARTICLE/BODY se usado direto
170172
- MANTÉM O ARTICLE/BODY via bs_ent2char
171173
- PERDE O ; APÓS LQUO E RQUO
172174
- Exemplo: &amp;lquoapostrophes&amp;rquo (sem ;)
173-
175+
174176
3. "html.parser" (Built-in do Python):
175177
- MANTÉM O ARTICLE/BODY
176178
- PERDE O ; APÓS LQUO E RQUO
177179
- Similar ao lxml mas mantém estrutura melhor
178-
180+
179181
4. "html5lib" (Parser HTML5):
180182
- ADICIONA <html><head></head><body>
181183
- Mantém entidades problemáticas como &amp;lquo; e &amp;rquo;
@@ -196,7 +198,7 @@ def bs_ent2char_(xml):
196198
def bs_ent2char(xml):
197199
"""
198200
Usa BeautifulSoup com parser lxml para converter entidades.
199-
201+
200202
Análise:
201203
- MANTÉM O ARTICLE/BODY, MAS PERDE O ; APÓS LQUO E RQUO
202204
- Converte a maioria das entidades HTML corretamente
@@ -220,11 +222,11 @@ def bs_ent2char(xml):
220222
</content>
221223
</body>
222224
</article>
223-
225+
224226
Vantagens:
225227
- Mantém estrutura XML original
226228
- Converte maioria das entidades HTML para caracteres Unicode
227-
229+
228230
Problemas:
229231
- Entidades &lquo; e &rquo; não são reconhecidas e perdem o ;
230232
- Tag <break/> é convertida para <break></break>
@@ -236,15 +238,15 @@ def bs_ent2char(xml):
236238
def main():
237239
"""
238240
Função principal para testar diferentes métodos de conversão de entidades.
239-
241+
240242
XML de entrada contém várias entidades HTML problemáticas:
241243
- &rsquo; &ldquo; &rdquo; &lquo; &rquo; (quotes)
242244
- &mdash; (travessão)
243245
- &nbsp; (espaço não quebrável)
244246
- &copy; &euro; &pound; (símbolos)
245247
- &frac12; &times; (matemáticos)
246248
- &#180; &#191; &#187; &#x02019; (numéricos)
247-
249+
248250
Resumo dos resultados:
249251
- xml_parser_ent2char: Remove entidades não reconhecidas
250252
- html_unescape_ent2char: Falha ao processar XML
@@ -298,7 +300,7 @@ def main():
298300
print("\n---\nload_xml")
299301
print(load_xml(xml))
300302

301-
303+
302304
if __name__ == "__main__":
303305
main()
304306

@@ -423,7 +425,7 @@ def main():
423425
---
424426
"""
425427

426-
# PERDE OS CARACTERES
428+
# PERDE OS CARACTERES
427429
"""
428430
xml
429431

packtools/sps/pid_provider/xml_sps_adapter.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,3 @@ def _str_with_64_char(text):
306306
if not text:
307307
return None
308308
return hashlib.sha256(_standardize(text).encode("utf-8")).hexdigest()
309-

0 commit comments

Comments
 (0)