summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHongxu Jia <hongxu.jia@windriver.com>2025-10-17 15:19:07 +0800
committerRichard Purdie <richard.purdie@linuxfoundation.org>2025-11-07 13:15:35 +0000
commit0176acd45298fb4d18ff82d4da96ccf50880ee45 (patch)
tree22520b4fecf3dfcc34db27fd88fc8ccb670d6159
parentd2b3a8575914f3f8254effd5b7cdd9498e90ed12 (diff)
downloadpoky-0176acd45298fb4d18ff82d4da96ccf50880ee45.tar.gz
itstool: Switch from libxml2 to lxml
Due to Deprecate libxml2 Python bindings [1], itstool switch from libxml2 to lxml [2] [1] https://gitlab.gnome.org/GNOME/libxml2/-/issues/891 [2] https://github.com/itstool/itstool/pull/57 (From OE-Core rev: fe3c87622f640c31cb0f66d5feab0efc3d043042) Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/recipes-support/itstool/itstool/0001-Switch-from-libxml2-to-lxml.patch1504
-rw-r--r--meta/recipes-support/itstool/itstool/0002-Fix-element-check.patch39
-rw-r--r--meta/recipes-support/itstool/itstool/0003-Rewrite-remaining-call-to-addContent.patch38
-rw-r--r--meta/recipes-support/itstool/itstool/0004-Fix-extraction-of-namespaced-attributes-from-langPoi.patch40
-rw-r--r--meta/recipes-support/itstool/itstool/0005-Handle-missing-placeholders-more-gracefully.patch38
-rw-r--r--meta/recipes-support/itstool/itstool_2.0.7.bb9
6 files changed, 1666 insertions, 2 deletions
diff --git a/meta/recipes-support/itstool/itstool/0001-Switch-from-libxml2-to-lxml.patch b/meta/recipes-support/itstool/itstool/0001-Switch-from-libxml2-to-lxml.patch
new file mode 100644
index 0000000000..71e685b528
--- /dev/null
+++ b/meta/recipes-support/itstool/itstool/0001-Switch-from-libxml2-to-lxml.patch
@@ -0,0 +1,1504 @@
1From 4ce4c7a81c2061ea06e8ee1e1b39980029d361af Mon Sep 17 00:00:00 2001
2From: Nick Wellnhofer <wellnhofer@aevum.de>
3Date: Thu, 16 Oct 2025 14:44:44 +0800
4Subject: [PATCH 1/5] Switch from libxml2 to lxml
5
6Most of the transition is straight-forward, but some issues turned up.
7
8- lxml doesn't seem to expose the prefixes of attributes, requiring
9 an XPath evaluation as work-around.
10- Serializing the internal subset is a bit hacky.
11- lxml doesn't support attribute nodes, so we have to emulate them.
12- lxml doesn't support attributes as XPath context nodes, so some use
13 cases aren't supported. Using an `.` expression on an attribute
14 works, though.
15
16Changes to expected test results are mostly cosmetic.
17
18- Whitespace before and after the document element is processed more
19 faithfully.
20- lxml removes some superfluous namespace prefixes.
21- There's one superfluous namespace declaration which isn't removed
22 anymore.
23
24Fixes #10
25
26Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de>
27
28Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/15027b5391e3d2c45846524721abbe978ca73def]
29Cherry pick from upstream and and rebase to 2.0.7, remove test
30Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com>
31---
32 configure.ac | 2 +-
33 itstool.in | 1012 +++++++++++++++++++++++---------------------------
34 2 files changed, 464 insertions(+), 550 deletions(-)
35
36diff --git a/configure.ac b/configure.ac
37index 9d04372..d94bead 100644
38--- a/configure.ac
39+++ b/configure.ac
40@@ -12,7 +12,7 @@ AC_SUBST([DATADIR])
41
42 AM_PATH_PYTHON([2.6])
43
44-py_module=libxml2
45+py_module=lxml
46 AC_MSG_CHECKING(for python module $py_module)
47 echo "import $py_module" | $PYTHON - &>/dev/null
48 if test $? -ne 0; then
49diff --git a/itstool.in b/itstool.in
50index daea177..4c73154 100755
51--- a/itstool.in
52+++ b/itstool.in
53@@ -24,7 +24,8 @@ DATADIR="@DATADIR@"
54
55 import gettext
56 import hashlib
57-import libxml2
58+from copy import deepcopy
59+from lxml import etree
60 import optparse
61 import os
62 import os.path
63@@ -190,7 +191,7 @@ class Comment (object):
64 class Placeholder (object):
65 def __init__ (self, node):
66 self.node = node
67- self.name = ustr(node.name, 'utf-8')
68+ self.name = ustr(xml_localname(node), 'utf-8')
69
70
71 class Message (object):
72@@ -243,32 +244,30 @@ class Message (object):
73 def add_start_tag (self, node):
74 if len(self._message) == 0 or not(isinstance(self._message[-1], string_types)):
75 self._message.append('')
76- if node.ns() is not None and node.ns().name is not None:
77- self._message[-1] += ('<%s:%s' % (ustr(node.ns().name, 'utf-8'), ustr(node.name, 'utf-8')))
78- else:
79- self._message[-1] += ('<%s' % ustr(node.name, 'utf-8'))
80- for prop in xml_attr_iter(node):
81- name = prop.name
82- if prop.ns() is not None:
83- name = prop.ns().name + ':' + name
84- atval = prop.content
85+ self._message[-1] += ('<%s' % ustr(xml_qname(node), 'utf-8'))
86+ for name, atval in node.items():
87+ qname = etree.QName(name)
88+ if qname.namespace is not None:
89+ # lxml doesn't expose the prefix of attributes, so we use
90+ # an XPath expression to get the attribute's prefixed name.
91+ # This is horribly inefficient.
92+ expr = 'name(@*[local-name()="%s" and namespace-uri()="%s"])' % (
93+ qname.localname, qname.namespace)
94+ name = node.xpath(expr)
95 if not isinstance(atval, ustr_type):
96 atval = ustr(atval, 'utf-8')
97 atval = atval.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
98 self._message += " %s=\"%s\"" % (name, atval)
99- if node.children is not None:
100+ if len(node) > 0 or node.text:
101 self._message[-1] += '>'
102 else:
103 self._message[-1] += '/>'
104
105 def add_end_tag (self, node):
106- if node.children is not None:
107+ if len(node) > 0 or node.text:
108 if len(self._message) == 0 or not(isinstance(self._message[-1], string_types)):
109 self._message.append('')
110- if node.ns() is not None and node.ns().name is not None:
111- self._message[-1] += ('</%s:%s>' % (ustr(node.ns().name, 'utf-8'), ustr(node.name, 'utf-8')))
112- else:
113- self._message[-1] += ('</%s>' % ustr(node.name, 'utf-8'))
114+ self._message[-1] += ('</%s>' % ustr(xml_qname(node), 'utf-8'))
115
116 def is_empty (self):
117 return self._empty
118@@ -379,67 +378,84 @@ class Message (object):
119 return ret
120
121
122-def xml_child_iter (node):
123- child = node.children
124- while child is not None:
125- yield child
126- child = child.next
127-
128-def xml_attr_iter (node):
129- attr = node.get_properties()
130- while attr is not None:
131- yield attr
132- attr = attr.next
133-
134-def xml_is_ns_name (node, ns, name):
135- if node.type != 'element':
136- return False
137- return node.name == name and node.ns() is not None and node.ns().content == ns
138+def xml_localname (node):
139+ return etree.QName(node.tag).localname
140+
141+def xml_qname (node):
142+ qname = etree.QName(node.tag).localname
143+ if node.prefix is not None:
144+ qname = node.prefix + ':' + qname
145+ return qname
146+
147+def xml_content (node):
148+ if isinstance(node, string_types):
149+ return node
150+ if isinstance(node, XMLAttr):
151+ return node.parent.get(node.tag)
152+ return etree.tostring(node, method='text', encoding='unicode')
153+
154+def xml_delete_node (node):
155+ parent = node.getparent()
156+ prev = node.getprevious()
157+ tail = node.tail
158+ if parent is not None:
159+ parent.remove(node)
160+ if prev is not None:
161+ if prev.tail is None or re.fullmatch(r'\s+', prev.tail):
162+ prev.tail = tail
163+ else:
164+ prev.tail += tail
165+ elif parent is not None:
166+ if parent.text is None or re.fullmatch(r'\s+', parent.text):
167+ parent.text = tail
168+ else:
169+ parent.text += tail
170
171 def xml_get_node_path(node):
172 # The built-in nodePath() method only does numeric indexes
173 # when necessary for disambiguation. For various reasons,
174 # we prefer always using indexes.
175- name = node.name
176- if node.ns() is not None and node.ns().name is not None:
177- name = node.ns().name + ':' + name
178- if node.type == 'attribute':
179+ name = xml_qname(node)
180+ if isinstance(node, XMLAttr):
181 name = '@' + name
182 name = '/' + name
183- if node.type == 'element' and node.parent.type == 'element':
184+ if node.getparent() is not None:
185 count = 1
186- prev = node.previousElementSibling()
187+ prev = node.getprevious()
188 while prev is not None:
189- if prev.name == node.name:
190- if prev.ns() is None:
191- if node.ns() is None:
192- count += 1
193- else:
194- if node.ns() is not None:
195- if prev.ns().name == node.ns().name:
196- count += 1
197- prev = prev.previousElementSibling()
198+ if prev.tag == node.tag:
199+ count += 1
200+ prev = prev.getprevious()
201 name = '%s[%i]' % (name, count)
202- if node.parent.type == 'element':
203- name = xml_get_node_path(node.parent) + name
204+ name = xml_get_node_path(node.getparent()) + name
205 return name
206
207-def xml_error_catcher(doc, error):
208- doc._xml_err += " %s" % error
209
210-def fix_node_ns (node, nsdefs):
211- childnsdefs = nsdefs.copy()
212- nsdef = node.nsDefs()
213- while nsdef is not None:
214- nextnsdef = nsdef.next
215- if nsdef.name in nsdefs and nsdefs[nsdef.name] == nsdef.content:
216- node.removeNsDef(nsdef.content)
217- else:
218- childnsdefs[nsdef.name] = nsdef.content
219- nsdef = nextnsdef
220- for child in xml_child_iter(node):
221- if child.type == 'element':
222- fix_node_ns(child, childnsdefs)
223+# lxml doesn't support attribute nodes, so we have to emulate them.
224+class XMLAttr (object):
225+ def __init__(self, element, tag):
226+ self.parent = element
227+ self.tag = tag
228+ self.attrib = {}
229+ self.sourceline = element.sourceline
230+
231+ def __repr__(self):
232+ return '%s@%s' % (repr(self.parent), self.tag)
233+
234+ def __eq__(self, other):
235+ return other and self.parent == other.parent and self.tag == other.tag
236+
237+ def __ne__(self, other):
238+ return not self.__eq__(other)
239+
240+ def __hash__(self):
241+ return hash(repr(self))
242+
243+ def getparent(self):
244+ return self.parent
245+
246+ def get(self, default=None):
247+ return default
248
249
250 class LocNote (object):
251@@ -464,82 +480,51 @@ class LocNote (object):
252
253 class Document (object):
254 def __init__ (self, filename, messages, load_dtd=False, keep_entities=False):
255- self._xml_err = ''
256- libxml2.registerErrorHandler(xml_error_catcher, self)
257- try:
258- ctxt = libxml2.createFileParserCtxt(filename)
259- except:
260- sys.stderr.write('Error: cannot open XML file %s\n' % filename)
261- sys.exit(1)
262- ctxt.lineNumbers(1)
263 self._load_dtd = load_dtd
264 self._keep_entities = keep_entities
265- if load_dtd:
266- ctxt.loadSubset(1)
267- if keep_entities:
268- ctxt.loadSubset(1)
269- ctxt.ctxtUseOptions(libxml2.XML_PARSE_DTDLOAD)
270- ctxt.replaceEntities(0)
271- else:
272- ctxt.replaceEntities(1)
273- ctxt.parseDocument()
274+ parser = etree.XMLParser(load_dtd = load_dtd or keep_entities,
275+ resolve_entities = not(keep_entities))
276+ doc = etree.parse(filename, parser)
277+ doc.xinclude()
278 self._filename = filename
279- self._doc = ctxt.doc()
280+ self._doc = doc
281 self._localrules = []
282- def pre_process (node):
283- for child in xml_child_iter(node):
284- if xml_is_ns_name(child, 'http://www.w3.org/2001/XInclude', 'include'):
285- if child.nsProp('parse', None) == 'text':
286- child.xincludeProcessTree()
287- elif xml_is_ns_name(child, NS_ITS, 'rules'):
288- if child.hasNsProp('href', NS_XLINK):
289- href = child.nsProp('href', NS_XLINK)
290- fileref = os.path.join(os.path.dirname(filename), href)
291- if not os.path.exists(fileref):
292- if opts.itspath is not None:
293- for pathdir in opts.itspath:
294- fileref = os.path.join(pathdir, href)
295- if os.path.exists(fileref):
296- break
297- if not os.path.exists(fileref):
298- sys.stderr.write('Error: Could not locate ITS file %s\n' % href)
299- sys.exit(1)
300- hctxt = libxml2.createFileParserCtxt(fileref)
301- hctxt.replaceEntities(1)
302- hctxt.parseDocument()
303- root = hctxt.doc().getRootElement()
304- version = None
305- if root.hasNsProp('version', None):
306- version = root.nsProp('version', None)
307- else:
308- sys.stderr.write('Warning: ITS file %s missing version attribute\n' %
309- os.path.basename(href))
310- if version is not None and version not in ('1.0', '2.0'):
311- sys.stderr.write('Warning: Skipping ITS file %s with unknown version %s\n' %
312- (os.path.basename(href), root.nsProp('version', None)))
313- else:
314- self._localrules.append(root)
315+ for child in doc.iter():
316+ if child.tag == '{' + NS_ITS + '}rules':
317+ href = child.get('{' + NS_XLINK + '}href')
318+ if href is not None:
319+ fileref = os.path.join(os.path.dirname(filename), href)
320+ if not os.path.exists(fileref):
321+ if opts.itspath is not None:
322+ for pathdir in opts.itspath:
323+ fileref = os.path.join(pathdir, href)
324+ if os.path.exists(fileref):
325+ break
326+ if not os.path.exists(fileref):
327+ sys.stderr.write('Error: Could not locate ITS file %s\n' % href)
328+ sys.exit(1)
329+ root = etree.parse(fileref).getroot()
330 version = None
331- if child.hasNsProp('version', None):
332- version = child.nsProp('version', None)
333+ version = root.get('version')
334+ if version is None:
335+ sys.stderr.write('Warning: ITS file %s missing version attribute\n' %
336+ os.path.basename(href))
337+ elif version not in ('1.0', '2.0'):
338+ sys.stderr.write('Warning: Skipping ITS file %s with unknown version %s\n' %
339+ (os.path.basename(href), root.get('version')))
340 else:
341- root = child.doc.getRootElement()
342- if root.hasNsProp('version', NS_ITS):
343- version = root.nsProp('version', NS_ITS)
344- else:
345- sys.stderr.write('Warning: Local ITS rules missing version attribute\n')
346- if version is not None and version not in ('1.0', '2.0'):
347- sys.stderr.write('Warning: Skipping local ITS rules with unknown version %s\n' %
348- version)
349- else:
350- self._localrules.append(child)
351- pre_process(child)
352- pre_process(self._doc)
353- try:
354- self._check_errors()
355- except libxml2.parserError as e:
356- sys.stderr.write('Error: Could not parse document:\n%s\n' % ustr(e))
357- sys.exit(1)
358+ self._localrules.append(root)
359+ version = child.get('version')
360+ if version is None:
361+ root = child.getroottree()
362+ version = root.get('{' + NS_ITS + '}version')
363+ if version is None:
364+ sys.stderr.write('Warning: Local ITS rules missing version attribute\n')
365+ elif version not in ('1.0', '2.0'):
366+ sys.stderr.write('Warning: Skipping local ITS rules with unknown version %s\n' %
367+ version)
368+ else:
369+ self._localrules.append(child)
370 self._msgs = messages
371 self._its_translate_nodes = {}
372 self._its_within_text_nodes = {}
373@@ -556,13 +541,6 @@ class Document (object):
374
375 self._clear_cache()
376
377- def __del__ (self):
378- self._doc.freeDoc()
379-
380- def _check_errors(self):
381- if self._xml_err:
382- raise libxml2.parserError(self._xml_err)
383-
384 def _clear_cache(self):
385 self._its_translate_nodes_cache = {}
386 self._its_locale_filters_cache = {}
387@@ -570,123 +548,107 @@ class Document (object):
388
389 def get_its_params(self, rules):
390 params = {}
391- for child in xml_child_iter(rules):
392- if xml_is_ns_name(child, NS_ITS, 'param'):
393- params[child.nsProp('name', None)] = child.getContent()
394+ for child in rules.iterchildren():
395+ if child.tag == '{' + NS_ITS + '}param':
396+ params[child.get('name')] = xml_content(child)
397 return params
398
399- def register_its_params(self, xpath, params, userparams={}):
400- for param in params:
401- if param in userparams:
402- xpath.xpathRegisterVariable(name, None, userparams[param])
403+ def register_its_params(self, var, params, userparams={}):
404+ for name in params:
405+ if name in userparams:
406+ var[name] = userparams[name]
407 else:
408- xpath.xpathRegisterVariable(name, None, params[param])
409+ var[name] = params[name]
410
411 def apply_its_rule(self, rule, xpath):
412 self._clear_cache()
413- if rule.type != 'element':
414- return
415- if xml_is_ns_name(rule, NS_ITS, 'translateRule'):
416- if rule.nsProp('selector', None) is not None:
417- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
418- self._its_translate_nodes[node] = rule.nsProp('translate', None)
419- elif xml_is_ns_name(rule, NS_ITS, 'withinTextRule'):
420- if rule.nsProp('selector', None) is not None:
421- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
422- self._its_within_text_nodes[node] = rule.nsProp('withinText', None)
423- elif xml_is_ns_name(rule, NS_ITST, 'preserveSpaceRule'):
424- if rule.nsProp('selector', None) is not None:
425- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
426- val = rule.nsProp('preserveSpace', None)
427+ if rule.tag == '{' + NS_ITS + '}translateRule':
428+ sel = rule.get('selector')
429+ if sel is not None:
430+ for node in self._try_xpath_eval(xpath, sel):
431+ self._its_translate_nodes[node] = rule.get('translate')
432+ elif rule.tag == '{' + NS_ITS + '}withinTextRule':
433+ sel = rule.get('selector')
434+ if sel is not None:
435+ for node in self._try_xpath_eval(xpath, sel):
436+ self._its_within_text_nodes[node] = rule.get('withinText')
437+ elif rule.tag == '{' + NS_ITST + '}preserveSpaceRule':
438+ sel = rule.get('selector')
439+ if sel is not None:
440+ for node in self._try_xpath_eval(xpath, sel):
441+ val = rule.get('preserveSpace')
442 if val == 'yes':
443 self._its_preserve_space_nodes[node] = 'preserve'
444- elif xml_is_ns_name(rule, NS_ITS, 'preserveSpaceRule'):
445- if rule.nsProp('selector', None) is not None:
446- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
447- self._its_preserve_space_nodes[node] = rule.nsProp('space', None)
448- elif xml_is_ns_name(rule, NS_ITS, 'localeFilterRule'):
449- if rule.nsProp('selector', None) is not None:
450- if rule.hasNsProp('localeFilterList', None):
451- lst = rule.nsProp('localeFilterList', None)
452- else:
453- lst = '*'
454- if rule.hasNsProp('localeFilterType', None):
455- typ = rule.nsProp('localeFilterType', None)
456- else:
457- typ = 'include'
458- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
459+ elif rule.tag == '{' + NS_ITS + '}preserveSpaceRule':
460+ sel = rule.get('selector')
461+ if sel is not None:
462+ for node in self._try_xpath_eval(xpath, sel):
463+ self._its_preserve_space_nodes[node] = rule.get('space')
464+ elif rule.tag == '{' + NS_ITS + '}localeFilterRule':
465+ sel = rule.get('selector')
466+ if sel is not None:
467+ lst = rule.get('localeFilterList', '*')
468+ typ = rule.get('localeFilterType', 'include')
469+ for node in self._try_xpath_eval(xpath, sel):
470 self._its_locale_filters[node] = (lst, typ)
471- elif xml_is_ns_name(rule, NS_ITST, 'dropRule'):
472- if rule.nsProp('selector', None) is not None:
473- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
474- self._itst_drop_nodes[node] = rule.nsProp('drop', None)
475- elif xml_is_ns_name(rule, NS_ITS, 'idValueRule'):
476- sel = rule.nsProp('selector', None)
477- idv = rule.nsProp('idValue', None)
478+ elif rule.tag == '{' + NS_ITST + '}dropRule':
479+ sel = rule.get('selector')
480+ if sel is not None:
481+ for node in self._try_xpath_eval(xpath, sel):
482+ self._itst_drop_nodes[node] = rule.get('drop')
483+ elif rule.tag == '{' + NS_ITS + '}idValueRule':
484+ sel = rule.get('selector')
485+ idv = rule.get('idValue')
486 if sel is not None and idv is not None:
487 for node in self._try_xpath_eval(xpath, sel):
488- try:
489- oldnode = xpath.contextNode()
490- except:
491- oldnode = None
492- xpath.setContextNode(node)
493- idvalue = self._try_xpath_eval(xpath, idv)
494+ idvalue = self._try_xpath_eval(xpath, idv, node=node)
495 if isinstance(idvalue, string_types):
496 self._its_id_values[node] = idvalue
497 else:
498 for val in idvalue:
499- self._its_id_values[node] = val.content
500+ self._its_id_values[node] = xml_content(val)
501 break
502- xpath.setContextNode(oldnode)
503 pass
504- elif xml_is_ns_name(rule, NS_ITST, 'contextRule'):
505- if rule.nsProp('selector', None) is not None:
506- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
507- if rule.hasNsProp('context', None):
508- self._itst_contexts[node] = rule.nsProp('context', None)
509- elif rule.hasNsProp('contextPointer', None):
510- try:
511- oldnode = xpath.contextNode()
512- except:
513- oldnode = None
514- xpath.setContextNode(node)
515- ctxt = self._try_xpath_eval(xpath, rule.nsProp('contextPointer', None))
516+ elif rule.tag == '{' + NS_ITST + '}contextRule':
517+ sel = rule.get('selector')
518+ if sel is not None:
519+ for node in self._try_xpath_eval(xpath, sel):
520+ ctxt = rule.get('context')
521+ cp = rule.get('contextPointer')
522+ if ctxt is not None:
523+ self._itst_contexts[node] = ctxt
524+ elif cp is not None:
525+ ctxt = self._try_xpath_eval(xpath, cp, node=node)
526 if isinstance(ctxt, string_types):
527 self._itst_contexts[node] = ctxt
528 else:
529 for ctxt in ctxt:
530- self._itst_contexts[node] = ctxt.content
531+ self._itst_contexts[node] = xml_content(ctxt)
532 break
533- xpath.setContextNode(oldnode)
534- elif xml_is_ns_name(rule, NS_ITS, 'locNoteRule'):
535+ elif rule.tag == '{' + NS_ITS + '}locNoteRule':
536 locnote = None
537- notetype = rule.nsProp('locNoteType', None)
538- for child in xml_child_iter(rule):
539- if xml_is_ns_name(child, NS_ITS, 'locNote'):
540- locnote = LocNote(locnote=child.content, locnotetype=notetype)
541- break
542+ notetype = rule.get('locNoteType')
543+ for child in rule.iterchildren('{' + NS_ITS + '}locNote'):
544+ locnote = LocNote(locnote=xml_content(child), locnotetype=notetype)
545+ break
546 if locnote is None:
547- if rule.hasNsProp('locNoteRef', None):
548- locnote = LocNote(locnoteref=rule.nsProp('locNoteRef', None), locnotetype=notetype)
549- if rule.nsProp('selector', None) is not None:
550- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
551+ if 'locNoteRef' in rule.attrib:
552+ locnote = LocNote(locnoteref=rule.get('locNoteRef'), locnotetype=notetype)
553+ sel = rule.get('selector')
554+ if sel is not None:
555+ for node in self._try_xpath_eval(xpath, sel):
556 if locnote is not None:
557 self._its_loc_notes.setdefault(node, []).append(locnote)
558 else:
559- if rule.hasNsProp('locNotePointer', None):
560- sel = rule.nsProp('locNotePointer', None)
561+ if 'locNotePointer' in rule.attrib:
562+ sel = rule.get('locNotePointer')
563 ref = False
564- elif rule.hasNsProp('locNoteRefPointer', None):
565- sel = rule.nsProp('locNoteRefPointer', None)
566+ elif 'locNoteRefPointer' in rule.attrib:
567+ sel = rule.get('locNoteRefPointer')
568 ref = True
569 else:
570 continue
571- try:
572- oldnode = xpath.contextNode()
573- except:
574- oldnode = None
575- xpath.setContextNode(node)
576- note = self._try_xpath_eval(xpath, sel)
577+ note = self._try_xpath_eval(xpath, sel, node=node)
578 if isinstance(note, string_types):
579 if ref:
580 nodenote = LocNote(locnoteref=note, locnotetype=notetype)
581@@ -695,55 +657,46 @@ class Document (object):
582 self._its_loc_notes.setdefault(node, []).append(nodenote)
583 else:
584 for note in note:
585+ text = xml_content(note)
586 if ref:
587- nodenote = LocNote(locnoteref=note.content, locnotetype=notetype)
588+ nodenote = LocNote(locnoteref=text, locnotetype=notetype)
589 else:
590- nodenote = LocNote(locnote=note.content, locnotetype=notetype,
591+ nodenote = LocNote(locnote=text, locnotetype=notetype,
592 space=self.get_preserve_space(note))
593 self._its_loc_notes.setdefault(node, []).append(nodenote)
594 break
595- xpath.setContextNode(oldnode)
596- elif xml_is_ns_name(rule, NS_ITS, 'langRule'):
597- if rule.nsProp('selector', None) is not None and rule.nsProp('langPointer', None) is not None:
598- for node in self._try_xpath_eval(xpath, rule.nsProp('selector', None)):
599- try:
600- oldnode = xpath.contextNode()
601- except:
602- oldnode = None
603- xpath.setContextNode(node)
604- res = self._try_xpath_eval(xpath, rule.nsProp('langPointer', None))
605+ elif rule.tag == '{' + NS_ITS + '}langRule':
606+ sel = rule.get('selector')
607+ lp = rule.get('langPointer')
608+ if sel is not None and lp is not None:
609+ for node in self._try_xpath_eval(xpath, sel):
610+ res = self._try_xpath_eval(xpath, lp, node=node)
611 if len(res) > 0:
612- self._its_lang[node] = res[0].content
613+ self._its_lang[node] = xml_content(res[0])
614 # We need to construct language attributes, not just read
615 # language information. Technically, langPointer could be
616 # any XPath expression. But if it looks like an attribute
617 # accessor, just use the attribute name.
618- if rule.nsProp('langPointer', None)[0] == '@':
619- self._itst_lang_attr[node] = rule.nsProp('langPointer', None)[1:]
620- xpath.setContextNode(oldnode)
621- elif xml_is_ns_name(rule, NS_ITST, 'credits'):
622- if rule.nsProp('appendTo', None) is not None:
623- for node in self._try_xpath_eval(xpath, rule.nsProp('appendTo', None)):
624+ if lp[0] == '@':
625+ self._itst_lang_attr[node] = lp[1:]
626+ elif rule.tag == '{' + NS_ITST + '}credits':
627+ sel = rule.get('appendTo')
628+ if sel is not None:
629+ for node in self._try_xpath_eval(xpath, sel):
630 self._itst_credits = (node, rule)
631 break
632- elif (xml_is_ns_name(rule, NS_ITS, 'externalResourceRefRule') or
633- xml_is_ns_name(rule, NS_ITST, 'externalRefRule')):
634- sel = rule.nsProp('selector', None)
635- if xml_is_ns_name(rule, NS_ITS, 'externalResourceRefRule'):
636- ptr = rule.nsProp('externalResourceRefPointer', None)
637+ elif (rule.tag == '{' + NS_ITS + '}externalResourceRefRule' or
638+ rule.tag == '{' + NS_ITST + '}externalRefRule'):
639+ sel = rule.get('selector')
640+ if rule.tag == '{' + NS_ITS + '}externalResourceRefRule':
641+ ptr = rule.get('externalResourceRefPointer')
642 else:
643- ptr = rule.nsProp('refPointer', None)
644+ ptr = rule.get('refPointer')
645 if sel is not None and ptr is not None:
646 for node in self._try_xpath_eval(xpath, sel):
647- try:
648- oldnode = xpath.contextNode()
649- except:
650- oldnode = None
651- xpath.setContextNode(node)
652- res = self._try_xpath_eval(xpath, ptr)
653+ res = self._try_xpath_eval(xpath, ptr, node=node)
654 if len(res) > 0:
655- self._its_externals[node] = res[0].content
656- xpath.setContextNode(oldnode)
657+ self._its_externals[node] = xml_content(res[0])
658
659 def apply_its_rules(self, builtins, userparams={}):
660 self._clear_cache()
661@@ -773,94 +726,59 @@ class Document (object):
662
663 def apply_its_file(self, filename, userparams={}):
664 self._clear_cache()
665- doc = libxml2.parseFile(filename)
666- root = doc.getRootElement()
667- if not xml_is_ns_name(root, NS_ITS, 'rules'):
668+ parser = etree.XMLParser(resolve_entities = False)
669+ root = etree.parse(filename, parser).getroot()
670+ if root.tag != '{' + NS_ITS + '}rules':
671 return
672- version = None
673- if root.hasNsProp('version', None):
674- version = root.nsProp('version', None)
675- else:
676+ version = root.get('version')
677+ if version is None:
678 sys.stderr.write('Warning: ITS file %s missing version attribute\n' %
679 os.path.basename(filename))
680- if version is not None and version not in ('1.0', '2.0'):
681+ elif version not in ('1.0', '2.0'):
682 sys.stderr.write('Warning: Skipping ITS file %s with unknown version %s\n' %
683- (os.path.basename(filename), root.nsProp('version', None)))
684+ (os.path.basename(filename), root.get('version')))
685 return
686 matched = True
687- for match in xml_child_iter(root):
688- if xml_is_ns_name(match, NS_ITST, 'match'):
689+ for match in root.iterchildren():
690+ if match.tag == '{' + NS_ITST + '}match':
691 matched = False
692- xpath = self._doc.xpathNewContext()
693- par = match
694- nss = {}
695- while par is not None:
696- nsdef = par.nsDefs()
697- while nsdef is not None:
698- if nsdef.name is not None:
699- if nsdef.name not in nss:
700- nss[nsdef.name] = nsdef.content
701- xpath.xpathRegisterNs(nsdef.name, nsdef.content)
702- nsdef = nsdef.next
703- par = par.parent
704- if match.hasNsProp('selector', None):
705- if len(self._try_xpath_eval(xpath, match.nsProp('selector', None))) > 0:
706+ sel = match.get('selector')
707+ if sel is not None:
708+ ns = { k: v for k, v in match.nsmap.items() if k is not None }
709+ xpath = (ns, {})
710+ if len(self._try_xpath_eval(xpath, sel)) > 0:
711 matched = True
712 break
713 if matched == False:
714 return
715+ ns = { k: v for k, v in match.nsmap.items() if k is not None }
716+ var = {}
717 params = self.get_its_params(root)
718- for rule in xml_child_iter(root):
719- xpath = self._doc.xpathNewContext()
720- par = match
721- nss = {}
722- while par is not None:
723- nsdef = par.nsDefs()
724- while nsdef is not None:
725- if nsdef.name is not None:
726- if nsdef.name not in nss:
727- nss[nsdef.name] = nsdef.content
728- xpath.xpathRegisterNs(nsdef.name, nsdef.content)
729- nsdef = nsdef.next
730- par = par.parent
731- self.register_its_params(xpath, params, userparams=userparams)
732+ self.register_its_params(var, params, userparams=userparams)
733+ xpath = (ns, var)
734+ for rule in root.iterchildren():
735 self.apply_its_rule(rule, xpath)
736
737 def apply_local_its_rules(self, userparams={}):
738 self._clear_cache()
739 for rules in self._localrules:
740- def reg_ns(xpath, node):
741- if node.parent is not None:
742- reg_ns(xpath, node.parent)
743- nsdef = node.nsDefs()
744- while nsdef is not None:
745- if nsdef.name is not None:
746- xpath.xpathRegisterNs(nsdef.name, nsdef.content)
747- nsdef = nsdef.next
748- xpath = self._doc.xpathNewContext()
749- reg_ns(xpath, rules)
750+ var = {}
751 params = self.get_its_params(rules)
752- self.register_its_params(xpath, params, userparams=userparams)
753- for rule in xml_child_iter(rules):
754- if rule.type != 'element':
755- continue
756- if rule.nsDefs() is not None:
757- rule_xpath = self._doc.xpathNewContext()
758- reg_ns(rule_xpath, rule)
759- self.register_its_params(rule_xpath, params, userparams=userparams)
760- else:
761- rule_xpath = xpath
762+ self.register_its_params(var, params, userparams=userparams)
763+ for rule in rules.iterchildren():
764+ ns = { k: v for k, v in rule.nsmap.items() if k is not None }
765+ rule_xpath = (ns, var)
766 self.apply_its_rule(rule, rule_xpath)
767
768 def _append_credits(self, parent, node, trdata):
769- if xml_is_ns_name(node, NS_ITST, 'for-each'):
770- select = node.nsProp('select', None)
771+ if node.tag == '{' + NS_ITST + '}for-each':
772+ select = node.get('select')
773 if select == 'years':
774 for year in trdata[2].split(','):
775- for child in xml_child_iter(node):
776+ for child in node.iterchildren():
777 self._append_credits(parent, child, trdata + (year.strip(),))
778- elif xml_is_ns_name(node, NS_ITST, 'value-of'):
779- select = node.nsProp('select', None)
780+ elif node.tag == '{' + NS_ITST + '}value-of':
781+ select = node.get('select')
782 val = None
783 if select == 'name':
784 val = trdata[0]
785@@ -875,9 +793,9 @@ class Document (object):
786 val = val.encode('utf-8')
787 parent.addContent(val)
788 else:
789- newnode = node.copyNode(2)
790- parent.addChild(newnode)
791- for child in xml_child_iter(node):
792+ newnode = parent.makeelement(node.tag, node.attrib)
793+ parent.append(newnode)
794+ for child in node.iterchildren():
795 self._append_credits(newnode, child, trdata)
796
797 def merge_credits(self, translations, language, node):
798@@ -895,7 +813,7 @@ class Document (object):
799 if not match:
800 continue
801 trdata = match.groups()
802- for node in xml_child_iter(self._itst_credits[1]):
803+ for node in self._itst_credits[1].iterchildren():
804 self._append_credits(self._itst_credits[0], node, trdata)
805
806 def join_translations(self, translations, node=None, strict=False):
807@@ -903,29 +821,30 @@ class Document (object):
808 if node is None:
809 is_root = True
810 self.generate_messages(comments=False)
811- node = self._doc.getRootElement()
812- if node is None or node.type != 'element':
813+ node = self._doc.getroot()
814+ if node is None:
815 return
816 if self.get_itst_drop(node) == 'yes':
817- prev = node.prev
818- node.unlinkNode()
819- node.freeNode()
820- if prev is not None and prev.isBlankNode():
821- prev.unlinkNode()
822- prev.freeNode()
823+ xml_delete_node(node)
824 return
825 msg = self._msgs.get_message_by_node(node)
826 if msg is None:
827- self.translate_attrs(node, node)
828- children = [child for child in xml_child_iter(node)]
829- for child in children:
830+ #self.translate_attrs(node, node)
831+ for child in node.iterchildren():
832 self.join_translations(translations, node=child, strict=strict)
833 else:
834- prevnode = None
835- if node.prev is not None and node.prev.type == 'text':
836- prevtext = node.prev.content
837- if re.sub('\s+', '', prevtext) == '':
838- prevnode = node.prev
839+ prevtext = None
840+ prev = node.getprevious()
841+ if prev is None:
842+ parent = node.getparent()
843+ if parent is not None:
844+ prevtext = parent.text
845+ else:
846+ prevtext = prev.tail
847+ if prevtext is not None:
848+ if not re.fullmatch(r'\s+', prevtext):
849+ prevtext = None
850+ i = 0
851 for lang in sorted(list(translations.keys()), reverse=True):
852 locale = self.get_its_locale_filter(node)
853 lmatch = match_locale_list(locale[0], lang)
854@@ -933,24 +852,26 @@ class Document (object):
855 continue
856 newnode = self.get_translated(node, translations[lang], strict=strict, lang=lang)
857 if newnode != node:
858- newnode.setProp('xml:lang', lang)
859- node.addNextSibling(newnode)
860- if prevnode is not None:
861- node.addNextSibling(prevnode.copyNode(0))
862- if is_root:
863- # Because of the way we create nodes and rewrite the document,
864- # we end up with lots of redundant namespace definitions. We
865- # kill them off in one fell swoop at the end.
866- fix_node_ns(node, {})
867- self._check_errors()
868+ newnode.set('{' + NS_XML + '}lang', lang)
869+ node.addnext(newnode)
870+ if i == 0:
871+ # Move tail to first new node
872+ newnode.tail = node.tail
873+ if prevtext is not None:
874+ node.tail = prevtext
875+ else:
876+ if prevtext is not None:
877+ newnode.tail = prevtext
878+ i += 1
879+
880
881 def merge_translations(self, translations, language, node=None, strict=False):
882 is_root = False
883 if node is None:
884 is_root = True
885 self.generate_messages(comments=False)
886- node = self._doc.getRootElement()
887- if node is None or node.type != 'element':
888+ node = self._doc.getroot()
889+ if node is None:
890 return
891 drop = False
892 locale = self.get_its_locale_filter(node)
893@@ -962,26 +883,23 @@ class Document (object):
894 if match_locale_list(locale[0], language):
895 drop = True
896 if self.get_itst_drop(node) == 'yes' or drop:
897- prev = node.prev
898- node.unlinkNode()
899- node.freeNode()
900- if prev is not None and prev.isBlankNode():
901- prev.unlinkNode()
902- prev.freeNode()
903+ xml_delete_node(node)
904 return
905 if is_root:
906 self.merge_credits(translations, language, node)
907 msg = self._msgs.get_message_by_node(node)
908 if msg is None:
909 self.translate_attrs(node, node)
910- children = [child for child in xml_child_iter(node)]
911- for child in children:
912+ for child in node.iterchildren():
913 self.merge_translations(translations, language, node=child, strict=strict)
914 else:
915 newnode = self.get_translated(node, translations, strict=strict, lang=language)
916 if newnode != node:
917 self.translate_attrs(node, newnode)
918- node.replaceNode(newnode)
919+ newnode.tail = node.tail
920+ parent = node.getparent()
921+ if parent is not None:
922+ parent.replace(node, newnode)
923 if is_root:
924 # Apply language attributes to untranslated nodes. We don't do
925 # this before processing, because then these attributes would
926@@ -998,31 +916,27 @@ class Document (object):
927 origlang = self._its_lang.get(lcpar)
928 if origlang is not None:
929 break
930- lcpar = lcpar.parent
931+ lcpar = lcpar.getparent()
932 if origlang is not None:
933- lcnode.setProp(attr, origlang)
934+ lcnode.set(attr, origlang)
935 # And then set the language attribute on the root node.
936 if language is not None:
937 attr = self._itst_lang_attr.get(node)
938 if attr is not None:
939- node.setProp(attr, language)
940- # Because of the way we create nodes and rewrite the document,
941- # we end up with lots of redundant namespace definitions. We
942- # kill them off in one fell swoop at the end.
943- fix_node_ns(node, {})
944- self._check_errors()
945+ node.set(attr, language)
946
947 def translate_attrs(self, oldnode, newnode):
948- trans_attrs = [attr for attr in xml_attr_iter(oldnode) if self._its_translate_nodes.get(attr, 'no') == 'yes']
949- for attr in trans_attrs:
950- srccontent = attr.get_content()
951+ for attrname, srccontent in oldnode.items():
952+ attr = XMLAttr(oldnode, attrname)
953+ if self._its_translate_nodes.get(attr, 'no') != 'yes':
954+ continue
955 if not PY3:
956 srccontent = srccontent.decode('utf-8')
957 newcontent = translations.ugettext(srccontent)
958 if newcontent:
959 if not PY3:
960 newcontent = newcontent.encode('utf-8')
961- newnode.setProp(attr.name, newcontent)
962+ newnode.set(attrname, newcontent)
963
964 def get_translated (self, node, translations, strict=False, lang=None):
965 msg = self._msgs.get_message_by_node(node)
966@@ -1037,106 +951,84 @@ class Document (object):
967 trans = translations.ugettext(msgstr)
968 if trans is None:
969 return node
970- nss = {}
971- def reg_ns(node, nss):
972- if node.parent is not None:
973- reg_ns(node.parent, nss)
974- nsdef = node.nsDefs()
975- while nsdef is not None:
976- nss[nsdef.name] = nsdef.content
977- nsdef = nsdef.next
978- reg_ns(node, nss)
979- nss['_'] = NS_BLANK
980- try:
981- blurb = node.doc.intSubset().serialize('utf-8')
982- except Exception:
983- blurb = ''
984- blurb += '<' + ustr(node.name, 'utf-8')
985- for nsname in list(nss.keys()):
986+ blurb = ''
987+ doc = node.getroottree()
988+ if doc.docinfo.internalDTD:
989+ # This is an ugly hack to serialize the DTD. We copy the
990+ # document, replace the document element, serialize the
991+ # document and remove the last line which contains the
992+ # document element, leaving only the DTD.
993+ copy = deepcopy(doc)
994+ root = copy.getroot()
995+ newroot = root.makeelement(root.tag)
996+ copy._setroot(newroot)
997+ blurb = re.sub('.*$', '', etree.tostring(copy, encoding='unicode'))
998+ localname = ustr(xml_localname(node), 'utf-8')
999+ blurb += '<' + localname
1000+ blurb += ' xmlns:_="%s"' % NS_BLANK
1001+ for nsname, nsuri in node.nsmap.items():
1002 if nsname is None:
1003- blurb += ' xmlns="%s"' % nss[nsname]
1004+ blurb += ' xmlns="%s"' % nsuri
1005 else:
1006- blurb += ' xmlns:%s="%s"' % (nsname, nss[nsname])
1007- blurb += '>%s</%s>' % (trans, ustr(node.name, 'utf-8'))
1008- if not PY3:
1009- blurb = blurb.encode('utf-8')
1010- ctxt = libxml2.createDocParserCtxt(blurb)
1011- if self._load_dtd:
1012- ctxt.loadSubset(1)
1013- if self._keep_entities:
1014- ctxt.loadSubset(1)
1015- ctxt.ctxtUseOptions(libxml2.XML_PARSE_DTDLOAD)
1016- ctxt.replaceEntities(0)
1017- else:
1018- ctxt.replaceEntities(1)
1019- ctxt.parseDocument()
1020- trnode = ctxt.doc().getRootElement()
1021+ blurb += ' xmlns:%s="%s"' % (nsname, nsuri)
1022+ blurb += '>%s</%s>' % (trans, localname)
1023+ parser = etree.XMLParser(load_dtd = self._load_dtd or self._keep_entities,
1024+ resolve_entities = not(self._keep_entities))
1025 try:
1026- self._check_errors()
1027- except libxml2.parserError:
1028+ trnode = etree.fromstring(blurb, parser)
1029+ except:
1030 if strict:
1031 raise
1032 else:
1033 sys.stderr.write('Warning: Could not merge %stranslation for msgid:\n%s\n' % (
1034- (lang + ' ') if lang is not None else '',
1035- msgstr.encode('utf-8')))
1036- self._xml_err = ''
1037+ (lang + ' ') if lang is not None else '',
1038+ msgstr.encode('utf-8')))
1039 return node
1040- def scan_node(node):
1041- children = [child for child in xml_child_iter(node)]
1042- for child in children:
1043- if child.type != 'element':
1044+ try:
1045+ for child in trnode.iterdescendants():
1046+ if isinstance(child, (etree._Entity, etree._Comment, etree._ProcessingInstruction)):
1047 continue
1048- if child.ns() is not None and child.ns().content == NS_BLANK:
1049- ph_node = msg.get_placeholder(child.name).node
1050- if self.has_child_elements(ph_node):
1051+ qname = etree.QName(child.tag)
1052+ if qname.namespace == NS_BLANK:
1053+ ph_node = msg.get_placeholder(qname.localname).node
1054+ if len(ph_node):
1055 self.merge_translations(translations, None, ph_node, strict=strict)
1056- newnode = ph_node.copyNode(1)
1057- newnode.setTreeDoc(self._doc)
1058- child.replaceNode(newnode)
1059+ newnode = deepcopy(ph_node)
1060+ newnode.tail = child.tail
1061+ child.getparent().replace(child, newnode)
1062 else:
1063 repl = self.get_translated(ph_node, translations, strict=strict, lang=lang)
1064- child.replaceNode(repl)
1065- scan_node(child)
1066- try:
1067- scan_node(trnode)
1068+ repl.tail = child.tail
1069+ child.getparent().replace(child, repl)
1070 except:
1071+ raise
1072 if strict:
1073 raise
1074 else:
1075 sys.stderr.write('Warning: Could not merge %stranslation for msgid:\n%s\n' % (
1076 (lang + ' ') if lang is not None else '',
1077 msgstr.encode('utf-8')))
1078- self._xml_err = ''
1079- ctxt.doc().freeDoc()
1080 return node
1081- retnode = node.copyNode(2)
1082- retnode.setTreeDoc(self._doc)
1083- for child in xml_child_iter(trnode):
1084- newnode = child.copyNode(1)
1085- newnode.setTreeDoc(self._doc)
1086- retnode.addChild(newnode)
1087-
1088- ctxt.doc().freeDoc()
1089+ retnode = self._doc.getroot().makeelement(node.tag, node.attrib, node.nsmap)
1090+ retnode.text = trnode.text
1091+ for child in trnode.iterchildren():
1092+ retnode.append(child)
1093+
1094 return retnode
1095
1096 def generate_messages(self, comments=True):
1097 if self._itst_credits is not None:
1098 self._msgs.add_credits()
1099- for child in xml_child_iter(self._doc):
1100- if child.type == 'element':
1101- self.generate_message(child, None, comments=comments)
1102- break
1103+ if self._doc is not None:
1104+ self.generate_message(self._doc.getroot(), None, comments=comments)
1105
1106 def generate_message(self, node, msg, comments=True, path=None):
1107- if node.type in ('text', 'cdata') and msg is not None:
1108- msg.add_text(node.content)
1109+ if isinstance(node, etree._Entity):
1110+ msg.add_entity_ref(node.name)
1111 return
1112- if node.type == 'entity_ref':
1113- msg.add_entity_ref(node.name);
1114- if node.type != 'element':
1115+ if isinstance(node, XMLAttr):
1116 return
1117- if node.hasNsProp('drop', NS_ITST) and node.nsProp('drop', NS_ITST) == 'yes':
1118+ if node.get('{' + NS_ITST + '}drop', 'no') == 'yes':
1119 return
1120 if self._itst_drop_nodes.get(node, 'no') == 'yes':
1121 return
1122@@ -1158,9 +1050,7 @@ class Document (object):
1123 if msg is not None:
1124 msg.add_placeholder(node)
1125 msg = Message()
1126- ctxt = None
1127- if node.hasNsProp('context', NS_ITST):
1128- ctxt = node.nsProp('context', NS_ITST)
1129+ ctxt = node.get('{' + NS_ITST + '}context')
1130 if ctxt is None:
1131 ctxt = self._itst_contexts.get(node)
1132 if ctxt is not None:
1133@@ -1173,27 +1063,38 @@ class Document (object):
1134 msg.set_preserve_space()
1135 if self.get_its_locale_filter(node) != ('*', 'include'):
1136 msg.set_locale_filter(self.get_its_locale_filter(node))
1137- msg.add_source('%s:%i' % (self._doc.name, node.lineNo()))
1138- msg.add_marker('%s/%s' % (ustr(node.parent.name, 'utf-8'), ustr(node.name, 'utf-8')))
1139+ msg.add_source('%s:%i' % (self._doc.docinfo.URL, node.sourceline))
1140+ parent = node.getparent()
1141+ if parent is None:
1142+ ptag = '#root'
1143+ else:
1144+ ptag = xml_localname(parent)
1145+ msg.add_marker('%s/%s' % (ustr(ptag, 'utf-8'), ustr(xml_localname(node), 'utf-8')))
1146 else:
1147 withinText = True
1148 msg.add_start_tag(node)
1149
1150 if not withinText:
1151 # Add msg for translatable node attributes
1152- for attr in xml_attr_iter(node):
1153+ for attrname, attrval in node.items():
1154+ attr = XMLAttr(node, attrname)
1155 if self._its_translate_nodes.get(attr, 'no') == 'yes':
1156 attr_msg = Message()
1157 if self.get_preserve_space(attr):
1158 attr_msg.set_preserve_space()
1159- attr_msg.add_source('%s:%i' % (self._doc.name, node.lineNo()))
1160- attr_msg.add_marker('%s/%s@%s' % (node.parent.name, node.name, attr.name))
1161- attr_msg.add_text(attr.content)
1162+ attr_msg.add_source('%s:%i' % (self._doc.docinfo.URL, node.sourceline))
1163+ attr_msg.add_marker('%s/%s@%s' % (
1164+ xml_localname(node.getparent()),
1165+ xml_localname(node),
1166+ etree.QName(attrname).localname))
1167+ attr_msg.add_text(attrval)
1168 if comments:
1169 for locnote in self.get_its_loc_notes(attr):
1170 comment = Comment(locnote)
1171 comment.add_marker ('%s/%s@%s' % (
1172- node.parent.name, node.name, attr.name))
1173+ xml_localname(node.getparent()),
1174+ xml_localname(node),
1175+ etree.QName(attrname).localname))
1176 attr_msg.add_comment(comment)
1177 self._msgs.add_message(attr_msg, attr)
1178
1179@@ -1204,15 +1105,16 @@ class Document (object):
1180 for locnote in self.get_its_loc_notes(cnode, inherit=(not withinText)):
1181 comment = Comment(locnote)
1182 if withinText:
1183- comment.add_marker('.%s/%s' % (path, cnode.name))
1184+ comment.add_marker('.%s/%s' % (path, xml_localname(cnode)))
1185 msg.add_comment(comment)
1186 hasnote = True
1187 if hasnote or not is_unit:
1188 break
1189- cnode = cnode.parent
1190+ cnode = cnode.getparent()
1191
1192 self.generate_external_resource_message(node)
1193- for attr in xml_attr_iter(node):
1194+ for attrname in node.keys():
1195+ attr = XMLAttr(node, attrname)
1196 self.generate_external_resource_message(attr)
1197 idvalue = self.get_its_id_value(attr)
1198 if idvalue is not None:
1199@@ -1220,9 +1122,13 @@ class Document (object):
1200 msg.add_id_value(basename + '#' + idvalue)
1201
1202 if withinText:
1203- path = path + '/' + node.name
1204- for child in xml_child_iter(node):
1205+ path = path + '/' + node.tag
1206+ if node.text is not None and msg is not None:
1207+ msg.add_text(node.text)
1208+ for child in node.iterchildren():
1209 self.generate_message(child, msg, comments=comments, path=path)
1210+ if child.tail is not None and msg is not None:
1211+ msg.add_text(child.tail)
1212
1213 if translate:
1214 if is_unit and not msg.is_empty():
1215@@ -1234,12 +1140,17 @@ class Document (object):
1216 if node not in self._its_externals:
1217 return
1218 resref = self._its_externals[node]
1219- if node.type == 'element':
1220- translate = self.get_its_translate(node)
1221- marker = '%s/%s' % (node.parent.name, node.name)
1222+ if isinstance(node, XMLAttr):
1223+ elem = node.getparent()
1224+ translate = self.get_its_translate(elem)
1225+ marker = '%s/%s/@%s' % (
1226+ xml_localname(elem.getparent()),
1227+ xml_localname(elem),
1228+ xml_localname(node))
1229 else:
1230- translate = self.get_its_translate(node.parent)
1231- marker = '%s/%s/@%s' % (node.parent.parent.name, node.parent.name, node.name)
1232+ translate = self.get_its_translate(node)
1233+ marker = '%s/%s' % (xml_localname(node.getparent()),
1234+ xml_localname(node))
1235 if translate == 'no':
1236 return
1237 msg = Message()
1238@@ -1253,7 +1164,7 @@ class Document (object):
1239 txt = "external ref='%s' md5='%s'" % (resref, filemd5)
1240 msg.set_context('_')
1241 msg.add_text(txt)
1242- msg.add_source('%s:%i' % (self._doc.name, node.lineNo()))
1243+ msg.add_source('%s:%i' % (self._doc.docinfo.URL, node.sourceline))
1244 msg.add_marker(marker)
1245 msg.add_comment(Comment('This is a reference to an external file such as an image or'
1246 ' video. When the file changes, the md5 hash will change to'
1247@@ -1265,44 +1176,41 @@ class Document (object):
1248 def is_translation_unit (self, node):
1249 return self.get_its_within_text(node) != 'yes'
1250
1251- def has_child_elements(self, node):
1252- return len([child for child in xml_child_iter(node) if child.type=='element'])
1253-
1254 def get_preserve_space (self, node):
1255- while node.type in ('attribute', 'element'):
1256- if node.getSpacePreserve() == 1:
1257+ while node is not None:
1258+ if node.get('{' + NS_XML + '}space') == 'preserve':
1259 return True
1260 if node in self._its_preserve_space_nodes:
1261 return (self._its_preserve_space_nodes[node] == 'preserve')
1262- node = node.parent
1263+ node = node.getparent()
1264 return False
1265
1266 def get_its_translate(self, node):
1267 if node in self._its_translate_nodes_cache:
1268 return self._its_translate_nodes_cache[node]
1269 val = None
1270- if node.hasNsProp('translate', NS_ITS):
1271- val = node.nsProp('translate', NS_ITS)
1272- elif xml_is_ns_name(node, NS_ITS, 'span') and node.hasNsProp('translate', None):
1273- val = node.nsProp('translate', None)
1274+ if '{' + NS_ITS + '}translate' in node.attrib:
1275+ val = node.get('{' + NS_ITS + '}translate')
1276+ elif node.tag == '{' + NS_ITS + '}span' and 'translate' in node.attrib:
1277+ val = node.get('translate')
1278 elif node in self._its_translate_nodes:
1279 val = self._its_translate_nodes[node]
1280 if val is not None:
1281 self._its_translate_nodes_cache[node] = val
1282 return val
1283- if node.type == 'attribute':
1284+ if isinstance(node, XMLAttr):
1285 return 'no'
1286- if node.parent.type == 'element':
1287- parval = self.get_its_translate(node.parent)
1288+ if node.getparent() is not None:
1289+ parval = self.get_its_translate(node.getparent())
1290 self._its_translate_nodes_cache[node] = parval
1291 return parval
1292 return 'yes'
1293
1294 def get_its_within_text(self, node):
1295- if node.hasNsProp('withinText', NS_ITS):
1296- val = node.nsProp('withinText', NS_ITS)
1297- elif xml_is_ns_name(node, NS_ITS, 'span') and node.hasNsProp('withinText', None):
1298- val = node.nsProp('withinText', None)
1299+ if '{' + NS_ITS + '}withinText' in node.attrib:
1300+ val = node.get('{' + NS_ITS + '}withinText')
1301+ elif node.tag == '{' + NS_ITS + '}span' and 'withinText' in node.attrib:
1302+ val = node.get('withinText')
1303 else:
1304 return self._its_within_text_nodes.get(node, 'no')
1305 if val in ('yes', 'nested'):
1306@@ -1312,73 +1220,63 @@ class Document (object):
1307 def get_its_locale_filter(self, node):
1308 if node in self._its_locale_filters_cache:
1309 return self._its_locale_filters_cache[node]
1310- if node.hasNsProp('localeFilterList', NS_ITS) or node.hasNsProp('localeFilterType', NS_ITS):
1311- if node.hasNsProp('localeFilterList', NS_ITS):
1312- lst = node.nsProp('localeFilterList', NS_ITS)
1313- else:
1314- lst = '*'
1315- if node.hasNsProp('localeFilterType', NS_ITS):
1316- typ = node.nsProp('localeFilterType', NS_ITS)
1317- else:
1318- typ = 'include'
1319+ if ('{' + NS_ITS + '}localeFilterList' in node.attrib or
1320+ '{' + NS_ITS + '}localeFilterType' in node.attrib):
1321+ lst = node.get('{' + NS_ITS + '}localeFilterList', '*')
1322+ typ = node.get('{' + NS_ITS + '}localeFilterType', 'include')
1323 return (lst, typ)
1324- if (xml_is_ns_name(node, NS_ITS, 'span') and
1325- (node.hasNsProp('localeFilterList', None) or node.hasNsProp('localeFilterType', None))):
1326- if node.hasNsProp('localeFilterList', None):
1327- lst = node.nsProp('localeFilterList', None)
1328- else:
1329- lst = '*'
1330- if node.hasNsProp('localeFilterType', None):
1331- typ = node.nsProp('localeFilterType', None)
1332- else:
1333- typ = 'include'
1334+ if (node.tag == '{' + NS_ITS + '}span' and
1335+ ('localeFilterList' in node.attrib or 'localeFilterType' in node.attrib)):
1336+ lst = node.get('localeFilterList', '*')
1337+ typ = node.get('localeFilterType', 'include')
1338 return (lst, typ)
1339 if node in self._its_locale_filters:
1340 return self._its_locale_filters[node]
1341- if node.parent.type == 'element':
1342- parval = self.get_its_locale_filter(node.parent)
1343+ if node.getparent() is not None:
1344+ parval = self.get_its_locale_filter(node.getparent())
1345 self._its_locale_filters_cache[node] = parval
1346 return parval
1347 return ('*', 'include')
1348
1349 def get_itst_drop(self, node):
1350- if node.hasNsProp('drop', NS_ITST) and node.nsProp('drop', NS_ITST) == 'yes':
1351+ if node.get('{' + NS_ITST + '}drop') == 'yes':
1352 return 'yes'
1353 if self._itst_drop_nodes.get(node, 'no') == 'yes':
1354 return 'yes'
1355 return 'no'
1356
1357 def get_its_id_value(self, node):
1358- if node.hasNsProp('id', NS_XML):
1359- return node.nsProp('id', NS_XML)
1360+ if '{' + NS_XML + '}id' in node.attrib:
1361+ return node.get('{' + NS_XML + '}id')
1362 return self._its_id_values.get(node, None)
1363
1364 def get_its_loc_notes(self, node, inherit=True):
1365 if node in self._its_loc_notes_cache:
1366 return self._its_loc_notes_cache[node]
1367 ret = []
1368- if ( node.hasNsProp('locNote', NS_ITS) or
1369- node.hasNsProp('locNoteRef', NS_ITS) or
1370- node.hasNsProp('locNoteType', NS_ITS) ):
1371- notetype = node.nsProp('locNoteType', NS_ITS)
1372- if node.hasNsProp('locNote', NS_ITS):
1373- ret.append(LocNote(locnote=node.nsProp('locNote', NS_ITS), locnotetype=notetype))
1374- elif node.hasNsProp('locNoteRef', NS_ITS):
1375- ret.append(LocNote(locnoteref=node.nsProp('locNoteRef', NS_ITS), locnotetype=notetype))
1376- elif xml_is_ns_name(node, NS_ITS, 'span'):
1377- if ( node.hasNsProp('locNote', None) or
1378- node.hasNsProp('locNoteRef', None) or
1379- node.hasNsProp('locNoteType', None) ):
1380- notetype = node.nsProp('locNoteType', None)
1381- if node.hasNsProp('locNote', None):
1382- ret.append(LocNote(locnote=node.nsProp('locNote', None), locnotetype=notetype))
1383- elif node.hasNsProp('locNoteRef', None):
1384- ret.append(LocNote(locnoteref=node.nsProp('locNoteRef', None), locnotetype=notetype))
1385+ if ( '{' + NS_ITS + '}locNote' in node.attrib or
1386+ '{' + NS_ITS + '}locNoteRef' in node.attrib or
1387+ '{' + NS_ITS + '}locNoteType' in node.attrib ):
1388+ notetype = node.get('{' + NS_ITS + '}locNoteType')
1389+ if '{' + NS_ITS + '}locNote' in node.attrib:
1390+ ret.append(LocNote(locnote=node.get('{' + NS_ITS + '}locNote'), locnotetype=notetype))
1391+ elif '{' + NS_ITS + '}locNoteRef' in node.attrib:
1392+ ret.append(LocNote(locnoteref=node.get('{' + NS_ITS + '}locNoteRef'), locnotetype=notetype))
1393+ elif node.tag == '{' + NS_ITS + '}span':
1394+ if ( 'locNote' in node.attrib or
1395+ 'locNoteRef' in node.attrib or
1396+ 'locNoteType' in node.attrib ):
1397+ notetype = node.get('locNoteType')
1398+ if 'locNote' in node.attrib:
1399+ ret.append(LocNote(locnote=node.get('locNote'), locnotetype=notetype))
1400+ elif 'locNoteRef' in node.attrib:
1401+ ret.append(LocNote(locnoteref=node.get('locNoteRef'), locnotetype=notetype))
1402 for locnote in reversed(self._its_loc_notes.get(node, [])):
1403 ret.append(locnote)
1404 if (len(ret) == 0 and inherit and
1405- node.type != 'attribute' and node.parent is not None and node.parent.type == 'element'):
1406- parval = self.get_its_loc_notes(node.parent)
1407+ not isinstance(node, XMLAttr) and
1408+ node.getparent() is not None):
1409+ parval = self.get_its_loc_notes(node.getparent())
1410 self._its_loc_notes_cache[node] = parval
1411 return parval
1412 self._its_loc_notes_cache[node] = ret
1413@@ -1386,12 +1284,12 @@ class Document (object):
1414
1415 def output_test_data(self, category, out, node=None):
1416 if node is None:
1417- node = self._doc.getRootElement()
1418+ node = self._doc.getroot()
1419 compval = ''
1420 if category == 'translate':
1421 compval = 'translate="%s"' % self.get_its_translate(node)
1422 elif category == 'withinText':
1423- if node.type != 'attribute':
1424+ if not isinstance(node, XMLAttr):
1425 compval = 'withinText="%s"' % self.get_its_within_text(node)
1426 elif category == 'localeFilter':
1427 compval = 'localeFilterList="%s"\tlocaleFilterType="%s"' % self.get_its_locale_filter(node)
1428@@ -1422,16 +1320,32 @@ class Document (object):
1429 out.write('%s\t%s\r\n' % (xml_get_node_path(node), compval))
1430 else:
1431 out.write('%s\r\n' % (xml_get_node_path(node)))
1432- for attr in sorted(xml_attr_iter(node), key=ustr):
1433+ for attrname in sorted(node.keys(), key=ustr):
1434+ attr = XMLAttr(node, attrname)
1435 self.output_test_data(category, out, attr)
1436- for child in xml_child_iter(node):
1437- if child.type == 'element':
1438- self.output_test_data(category, out, child)
1439+ for child in node.iterchildren():
1440+ self.output_test_data(category, out, child)
1441
1442- @staticmethod
1443- def _try_xpath_eval (xpath, expr):
1444+ def _try_xpath_eval (self, xpath, expr, node=None):
1445+ if node is None:
1446+ node = self._doc
1447+ elif isinstance(node, XMLAttr):
1448+ # lxml doesn't support attributes as XPath context nodes.
1449+ if expr == '.':
1450+ return [ node ]
1451+ sys.stderr.write('Warning: Unsupported XPath on attribute: %s\n' % expr)
1452+ return []
1453 try:
1454- return xpath.xpathEval(expr)
1455+ result = node.xpath(expr, namespaces=xpath[0], **xpath[1])
1456+ if not isinstance(result, str):
1457+ for i in range(len(result)):
1458+ val = result[i]
1459+ # Use lxml's "smart string" feature to determine
1460+ # the attribute node.
1461+ if (isinstance(val, etree._ElementUnicodeResult) and
1462+ val.is_attribute):
1463+ result[i] = XMLAttr(val.getparent(), val.attrname)
1464+ return result
1465 except:
1466 sys.stderr.write('Warning: Invalid XPath: %s\n' % expr)
1467 return []
1468@@ -1636,11 +1550,11 @@ if __name__ == '__main__':
1469 raise
1470 sys.stderr.write('Error: Could not merge translations:\n%s\n' % ustr(e))
1471 sys.exit(1)
1472- serialized = doc._doc.serialize('utf-8')
1473- if PY3:
1474- # For some reason, under py3, our serialized data is returns as a str.
1475- # Let's encode it to bytes
1476- serialized = serialized.encode('utf-8')
1477+ # lxml generates XML declarations with single quotes.
1478+ serialized = (
1479+ b'<?xml version="1.0" encoding="utf-8"?>\n' +
1480+ etree.tostring(doc._doc, encoding='utf-8') +
1481+ b'\n')
1482 fout = out
1483 fout_is_str = isinstance(fout, string_types)
1484 if fout_is_str:
1485@@ -1675,11 +1589,11 @@ if __name__ == '__main__':
1486 for itsfile in opts.itsfile:
1487 doc.apply_its_file(itsfile, userparams=userparams)
1488 doc.join_translations(translations, strict=opts.strict)
1489- serialized = doc._doc.serialize('utf-8')
1490- if PY3:
1491- # For some reason, under py3, our serialized data is returns as a str.
1492- # Let's encode it to bytes
1493- serialized = serialized.encode('utf-8')
1494+ # lxml generates XML declarations with single quotes.
1495+ serialized = (
1496+ b'<?xml version="1.0" encoding="utf-8"?>\n' +
1497+ etree.tostring(doc._doc, encoding='utf-8') +
1498+ b'\n')
1499 out.write(serialized)
1500 out.flush()
1501
1502--
15032.34.1
1504
diff --git a/meta/recipes-support/itstool/itstool/0002-Fix-element-check.patch b/meta/recipes-support/itstool/itstool/0002-Fix-element-check.patch
new file mode 100644
index 0000000000..a5228856a1
--- /dev/null
+++ b/meta/recipes-support/itstool/itstool/0002-Fix-element-check.patch
@@ -0,0 +1,39 @@
1From 97016d8de27584df3baa327509a53b767c3b79f3 Mon Sep 17 00:00:00 2001
2From: Nick Wellnhofer <wellnhofer@aevum.de>
3Date: Mon, 12 May 2025 17:11:31 +0200
4Subject: [PATCH 2/5] Fix element check
5
6Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de>
7
8Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/30289d5b532bb888f2e6099c04976e441141dd01]
9Cherry pick from upstream and and rebase to 2.0.7
10Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com>
11---
12 itstool.in | 4 ++--
13 1 file changed, 2 insertions(+), 2 deletions(-)
14
15diff --git a/itstool.in b/itstool.in
16index 4c73154..4f1f44f 100755
17--- a/itstool.in
18+++ b/itstool.in
19@@ -864,7 +864,6 @@ class Document (object):
20 newnode.tail = prevtext
21 i += 1
22
23-
24 def merge_translations(self, translations, language, node=None, strict=False):
25 is_root = False
26 if node is None:
27@@ -1026,7 +1025,8 @@ class Document (object):
28 if isinstance(node, etree._Entity):
29 msg.add_entity_ref(node.name)
30 return
31- if isinstance(node, XMLAttr):
32+ # Only allow elements
33+ if isinstance(node, XMLAttr) or not isinstance(node.tag, str):
34 return
35 if node.get('{' + NS_ITST + '}drop', 'no') == 'yes':
36 return
37--
382.34.1
39
diff --git a/meta/recipes-support/itstool/itstool/0003-Rewrite-remaining-call-to-addContent.patch b/meta/recipes-support/itstool/itstool/0003-Rewrite-remaining-call-to-addContent.patch
new file mode 100644
index 0000000000..4811812c97
--- /dev/null
+++ b/meta/recipes-support/itstool/itstool/0003-Rewrite-remaining-call-to-addContent.patch
@@ -0,0 +1,38 @@
1From 6ce1d67e698b77cf8f80477a48bb9d35d70374f5 Mon Sep 17 00:00:00 2001
2From: Nick Wellnhofer <wellnhofer@aevum.de>
3Date: Tue, 27 May 2025 20:15:56 +0200
4Subject: [PATCH 3/5] Rewrite remaining call to `addContent`
5
6Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de>
7
8Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/0d79db8eacc6787b397caa34f0d849afec52c582]
9Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com>
10---
11 itstool.in | 11 ++++++++++-
12 1 file changed, 10 insertions(+), 1 deletion(-)
13
14diff --git a/itstool.in b/itstool.in
15index 4f1f44f..602bc42 100755
16--- a/itstool.in
17+++ b/itstool.in
18@@ -791,7 +791,16 @@ class Document (object):
19 if val is not None:
20 if not PY3:
21 val = val.encode('utf-8')
22- parent.addContent(val)
23+ if len(parent):
24+ if parent[-1].tail:
25+ parent[-1].tail += val
26+ else:
27+ parent[-1].tail = val
28+ else:
29+ if parent.text:
30+ parent.text += val
31+ else:
32+ parent.text = val
33 else:
34 newnode = parent.makeelement(node.tag, node.attrib)
35 parent.append(newnode)
36--
372.34.1
38
diff --git a/meta/recipes-support/itstool/itstool/0004-Fix-extraction-of-namespaced-attributes-from-langPoi.patch b/meta/recipes-support/itstool/itstool/0004-Fix-extraction-of-namespaced-attributes-from-langPoi.patch
new file mode 100644
index 0000000000..5cfe64c874
--- /dev/null
+++ b/meta/recipes-support/itstool/itstool/0004-Fix-extraction-of-namespaced-attributes-from-langPoi.patch
@@ -0,0 +1,40 @@
1From e30c21e9d5f7db107b8fb2c8008772b740b5ea21 Mon Sep 17 00:00:00 2001
2From: Nick Wellnhofer <wellnhofer@aevum.de>
3Date: Tue, 27 May 2025 21:45:06 +0200
4Subject: [PATCH 4/5] Fix extraction of namespaced attributes from langPointer
5
6Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de>
7
8Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/d8c9a667e6af706cc7729e3d90b8f80b12c4f80e]
9Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com>
10---
11 itstool.in | 12 +++++++++++-
12 1 file changed, 11 insertions(+), 1 deletion(-)
13
14diff --git a/itstool.in b/itstool.in
15index 602bc42..e94c58a 100755
16--- a/itstool.in
17+++ b/itstool.in
18@@ -677,8 +677,18 @@ class Document (object):
19 # language information. Technically, langPointer could be
20 # any XPath expression. But if it looks like an attribute
21 # accessor, just use the attribute name.
22+ # TODO: This should probably be skipped if langPointer
23+ # equals '@xml:lang' which is the default.
24 if lp[0] == '@':
25- self._itst_lang_attr[node] = lp[1:]
26+ name = lp[1:]
27+ if ':' in name:
28+ prefix, lname = name.split(':', 2)
29+ nsuri = node.nsmap.get(prefix)
30+ if nsuri is None:
31+ name = lname
32+ else:
33+ name = '{' + nsuri + '}' + lname
34+ self._itst_lang_attr[node] = name
35 elif rule.tag == '{' + NS_ITST + '}credits':
36 sel = rule.get('appendTo')
37 if sel is not None:
38--
392.34.1
40
diff --git a/meta/recipes-support/itstool/itstool/0005-Handle-missing-placeholders-more-gracefully.patch b/meta/recipes-support/itstool/itstool/0005-Handle-missing-placeholders-more-gracefully.patch
new file mode 100644
index 0000000000..2b8805b5da
--- /dev/null
+++ b/meta/recipes-support/itstool/itstool/0005-Handle-missing-placeholders-more-gracefully.patch
@@ -0,0 +1,38 @@
1From 4c9a072395a38933ddb5d1dc5a18d2d1585a9501 Mon Sep 17 00:00:00 2001
2From: Nick Wellnhofer <wellnhofer@aevum.de>
3Date: Wed, 28 May 2025 19:46:44 +0200
4Subject: [PATCH 5/5] Handle missing placeholders more gracefully
5
6Print a warning to stderr instead of crashing.
7
8This is not related to the lxml migration but to issue #1.
9
10Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de>
11
12Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/0f6751a586422719442eb7e9ddfe635ec7ca06fb]
13Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com>
14---
15 itstool.in | 7 ++++++-
16 1 file changed, 6 insertions(+), 1 deletion(-)
17
18diff --git a/itstool.in b/itstool.in
19index e94c58a..47b108b 100755
20--- a/itstool.in
21+++ b/itstool.in
22@@ -1008,7 +1008,12 @@ class Document (object):
23 continue
24 qname = etree.QName(child.tag)
25 if qname.namespace == NS_BLANK:
26- ph_node = msg.get_placeholder(qname.localname).node
27+ ph = msg.get_placeholder(qname.localname)
28+ if ph is None:
29+ sys.stderr.write('Warning: Could not find placeholder %s\n' % (
30+ qname.localname))
31+ continue
32+ ph_node = ph.node
33 if len(ph_node):
34 self.merge_translations(translations, None, ph_node, strict=strict)
35 newnode = deepcopy(ph_node)
36--
372.34.1
38
diff --git a/meta/recipes-support/itstool/itstool_2.0.7.bb b/meta/recipes-support/itstool/itstool_2.0.7.bb
index ddbc49b6cf..9cc6d70e39 100644
--- a/meta/recipes-support/itstool/itstool_2.0.7.bb
+++ b/meta/recipes-support/itstool/itstool_2.0.7.bb
@@ -12,9 +12,14 @@ LIC_FILES_CHKSUM = "\
12 12
13inherit autotools python3native 13inherit autotools python3native
14 14
15DEPENDS = "libxml2-native" 15DEPENDS = "python3-lxml-native"
16 16
17SRC_URI = "http://files.itstool.org/${BPN}/${BPN}-${PV}.tar.bz2 \ 17SRC_URI = "http://files.itstool.org/${BPN}/${BPN}-${PV}.tar.bz2 \
18 file://0001-Switch-from-libxml2-to-lxml.patch \
19 file://0002-Fix-element-check.patch \
20 file://0003-Rewrite-remaining-call-to-addContent.patch \
21 file://0004-Fix-extraction-of-namespaced-attributes-from-langPoi.patch \
22 file://0005-Handle-missing-placeholders-more-gracefully.patch \
18 " 23 "
19UPSTREAM_CHECK_URI = "https://itstool.org/download.html" 24UPSTREAM_CHECK_URI = "https://itstool.org/download.html"
20 25
@@ -26,4 +31,4 @@ SRC_URI[sha256sum] = "6b9a7cd29a12bb95598f5750e8763cee78836a1a207f85b74d8b3275b2
26 31
27BBCLASSEXTEND = "native nativesdk" 32BBCLASSEXTEND = "native nativesdk"
28 33
29RDEPENDS:${PN} += "libxml2-python" 34RDEPENDS:${PN} += "python3-lxml"