diff options
| author | Hongxu Jia <hongxu.jia@windriver.com> | 2025-10-17 15:19:07 +0800 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2025-11-07 13:15:35 +0000 |
| commit | 0176acd45298fb4d18ff82d4da96ccf50880ee45 (patch) | |
| tree | 22520b4fecf3dfcc34db27fd88fc8ccb670d6159 | |
| parent | d2b3a8575914f3f8254effd5b7cdd9498e90ed12 (diff) | |
| download | poky-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>
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 @@ | |||
| 1 | From 4ce4c7a81c2061ea06e8ee1e1b39980029d361af Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 3 | Date: Thu, 16 Oct 2025 14:44:44 +0800 | ||
| 4 | Subject: [PATCH 1/5] Switch from libxml2 to lxml | ||
| 5 | |||
| 6 | Most 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 | |||
| 16 | Changes 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 | |||
| 24 | Fixes #10 | ||
| 25 | |||
| 26 | Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 27 | |||
| 28 | Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/15027b5391e3d2c45846524721abbe978ca73def] | ||
| 29 | Cherry pick from upstream and and rebase to 2.0.7, remove test | ||
| 30 | Signed-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 | |||
| 36 | diff --git a/configure.ac b/configure.ac | ||
| 37 | index 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 | ||
| 49 | diff --git a/itstool.in b/itstool.in | ||
| 50 | index 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('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') | ||
| 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 | -- | ||
| 1503 | 2.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 @@ | |||
| 1 | From 97016d8de27584df3baa327509a53b767c3b79f3 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 3 | Date: Mon, 12 May 2025 17:11:31 +0200 | ||
| 4 | Subject: [PATCH 2/5] Fix element check | ||
| 5 | |||
| 6 | Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 7 | |||
| 8 | Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/30289d5b532bb888f2e6099c04976e441141dd01] | ||
| 9 | Cherry pick from upstream and and rebase to 2.0.7 | ||
| 10 | Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com> | ||
| 11 | --- | ||
| 12 | itstool.in | 4 ++-- | ||
| 13 | 1 file changed, 2 insertions(+), 2 deletions(-) | ||
| 14 | |||
| 15 | diff --git a/itstool.in b/itstool.in | ||
| 16 | index 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 | -- | ||
| 38 | 2.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 @@ | |||
| 1 | From 6ce1d67e698b77cf8f80477a48bb9d35d70374f5 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 3 | Date: Tue, 27 May 2025 20:15:56 +0200 | ||
| 4 | Subject: [PATCH 3/5] Rewrite remaining call to `addContent` | ||
| 5 | |||
| 6 | Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 7 | |||
| 8 | Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/0d79db8eacc6787b397caa34f0d849afec52c582] | ||
| 9 | Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com> | ||
| 10 | --- | ||
| 11 | itstool.in | 11 ++++++++++- | ||
| 12 | 1 file changed, 10 insertions(+), 1 deletion(-) | ||
| 13 | |||
| 14 | diff --git a/itstool.in b/itstool.in | ||
| 15 | index 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 | -- | ||
| 37 | 2.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 @@ | |||
| 1 | From e30c21e9d5f7db107b8fb2c8008772b740b5ea21 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 3 | Date: Tue, 27 May 2025 21:45:06 +0200 | ||
| 4 | Subject: [PATCH 4/5] Fix extraction of namespaced attributes from langPointer | ||
| 5 | |||
| 6 | Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 7 | |||
| 8 | Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/d8c9a667e6af706cc7729e3d90b8f80b12c4f80e] | ||
| 9 | Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com> | ||
| 10 | --- | ||
| 11 | itstool.in | 12 +++++++++++- | ||
| 12 | 1 file changed, 11 insertions(+), 1 deletion(-) | ||
| 13 | |||
| 14 | diff --git a/itstool.in b/itstool.in | ||
| 15 | index 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 | -- | ||
| 39 | 2.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 @@ | |||
| 1 | From 4c9a072395a38933ddb5d1dc5a18d2d1585a9501 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 3 | Date: Wed, 28 May 2025 19:46:44 +0200 | ||
| 4 | Subject: [PATCH 5/5] Handle missing placeholders more gracefully | ||
| 5 | |||
| 6 | Print a warning to stderr instead of crashing. | ||
| 7 | |||
| 8 | This is not related to the lxml migration but to issue #1. | ||
| 9 | |||
| 10 | Signed-off-by: Nick Wellnhofer <wellnhofer@aevum.de> | ||
| 11 | |||
| 12 | Upstream-Status: Submitted [https://github.com/itstool/itstool/pull/57/commits/0f6751a586422719442eb7e9ddfe635ec7ca06fb] | ||
| 13 | Signed-off-by: Hongxu Jia <hongxu.jia@windriver.com> | ||
| 14 | --- | ||
| 15 | itstool.in | 7 ++++++- | ||
| 16 | 1 file changed, 6 insertions(+), 1 deletion(-) | ||
| 17 | |||
| 18 | diff --git a/itstool.in b/itstool.in | ||
| 19 | index 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 | -- | ||
| 37 | 2.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 | ||
| 13 | inherit autotools python3native | 13 | inherit autotools python3native |
| 14 | 14 | ||
| 15 | DEPENDS = "libxml2-native" | 15 | DEPENDS = "python3-lxml-native" |
| 16 | 16 | ||
| 17 | SRC_URI = "http://files.itstool.org/${BPN}/${BPN}-${PV}.tar.bz2 \ | 17 | SRC_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 | " |
| 19 | UPSTREAM_CHECK_URI = "https://itstool.org/download.html" | 24 | UPSTREAM_CHECK_URI = "https://itstool.org/download.html" |
| 20 | 25 | ||
| @@ -26,4 +31,4 @@ SRC_URI[sha256sum] = "6b9a7cd29a12bb95598f5750e8763cee78836a1a207f85b74d8b3275b2 | |||
| 26 | 31 | ||
| 27 | BBCLASSEXTEND = "native nativesdk" | 32 | BBCLASSEXTEND = "native nativesdk" |
| 28 | 33 | ||
| 29 | RDEPENDS:${PN} += "libxml2-python" | 34 | RDEPENDS:${PN} += "python3-lxml" |
