Date: Sunday, June 27, 2021 @ 18:39:29 Author: muflone Revision: 967606
archrelease: copy trunk to community-any Added: gimp-help/repos/community-any/PKGBUILD (from rev 967605, gimp-help/trunk/PKGBUILD) gimp-help/repos/community-any/gimp-help-2.10.0-python3.patch (from rev 967605, gimp-help/trunk/gimp-help-2.10.0-python3.patch) Deleted: gimp-help/repos/community-any/PKGBUILD --------------------------------+ PKGBUILD | 134 ++--- gimp-help-2.10.0-python3.patch | 975 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1044 insertions(+), 65 deletions(-) Deleted: PKGBUILD =================================================================== --- PKGBUILD 2021-06-27 18:38:55 UTC (rev 967605) +++ PKGBUILD 2021-06-27 18:39:29 UTC (rev 967606) @@ -1,65 +0,0 @@ -# Maintainer: Muflone http://www.muflone.com/contacts/english/ -# Contributor: Piotr Rogoża <rogoza dot piotr at gmail dot com> - -pkgbase=gimp-help -pkgver=2.10.0 -pkgrel=3 -arch=('any') -url='https://docs.gimp.org/' -license=('GPL' 'LGPL') -makedepends=('python2' 'docbook-xsl') -options=(!strip !zipman) -source=("https://ftp.gimp.org/pub/gimp/help/${pkgbase}-${pkgver}.tar.bz2") -sha256sums=('03804fed071b49e5810edd8327868659dfd9932fbf34d34189d56bd0ad539118') - -_languages=( - 'ca "Catalan"' - 'da "Danish"' - 'de "German"' - 'el "Greek"' - 'en "English"' - 'en_GB "English (United Kingdom)"' - 'es "Spanish"' - 'fi "Finnish"' - 'fr "French"' - 'it "Italian"' - 'ja "Japanese"' - 'ko "Korean"' - 'nl "Dutch"' - 'nn "Norwegian"' - 'pt_BR "Brazilian Portuguese"' - 'ro "Romanian"' - 'ru "Russian"' - 'zh_CN "Chinese (simplified)"' -) - -_package() { - _locale="$1" - _language="$2" - pkgdesc="${_language} help files for Gimp" - install -dm755 "${pkgdir}/usr/share/gimp/2.0/help/${_locale}" - cp -rL "${srcdir}/${pkgbase}-${pkgver}/html/${_locale}" \ - "${pkgdir}/usr/share/gimp/2.0/help" -} - -prepare() { - cd "${pkgbase}-${pkgver}" - sed -e '1s/python$/python2/' -i "tools/xml2po.py" -} - -build(){ - cd "${pkgbase}-${pkgver}" - ./configure --prefix=/usr --without-gimp - make -} - -for _lang in "${_languages[@]}" -do - _locale=${_lang%% *} - _pkgname=${pkgbase}-${_locale,,} - - pkgname+=(${_pkgname}) - eval "package_${_pkgname}() { - _package ${_lang} - }" -done Copied: gimp-help/repos/community-any/PKGBUILD (from rev 967605, gimp-help/trunk/PKGBUILD) =================================================================== --- PKGBUILD (rev 0) +++ PKGBUILD 2021-06-27 18:39:29 UTC (rev 967606) @@ -0,0 +1,69 @@ +# Maintainer: Muflone http://www.muflone.com/contacts/english/ +# Contributor: Piotr Rogoża <rogoza dot piotr at gmail dot com> + +pkgbase=gimp-help +pkgver=2.10.0 +pkgrel=4 +arch=('any') +url='https://docs.gimp.org/' +license=('GPL' 'LGPL') +makedepends=('python' 'docbook-xsl') +options=(!strip !zipman) +source=("https://ftp.gimp.org/pub/gimp/help/${pkgbase}-${pkgver}.tar.bz2" + "gimp-help-2.10.0-python3.patch") +sha256sums=('03804fed071b49e5810edd8327868659dfd9932fbf34d34189d56bd0ad539118' + '955a8599f6908b989252a63ea3227198ff9880f30fc254fccc5128a6a745d599') + +_languages=( + 'ca "Catalan"' + 'da "Danish"' + 'de "German"' + 'el "Greek"' + 'en "English"' + 'en_GB "English (United Kingdom)"' + 'es "Spanish"' + 'fi "Finnish"' + 'fr "French"' + 'it "Italian"' + 'ja "Japanese"' + 'ko "Korean"' + 'nl "Dutch"' + 'nn "Norwegian"' + 'pt_BR "Brazilian Portuguese"' + 'ro "Romanian"' + 'ru "Russian"' + 'zh_CN "Chinese (simplified)"' +) + +_package() { + _locale="$1" + _language="$2" + pkgdesc="${_language} help files for Gimp" + install -dm755 "${pkgdir}/usr/share/gimp/2.0/help/${_locale}" + cp -rL "${srcdir}/${pkgbase}-${pkgver}/html/${_locale}" \ + "${pkgdir}/usr/share/gimp/2.0/help" +} + +prepare() { + cd "${pkgbase}-${pkgver}" + sed -e '1s/python$/python2/' -i "tools/xml2po.py" + # Python 3 (FS#70078 https://bugs.archlinux.org/task/70078 ) + patch -Np1 -i "${srcdir}/gimp-help-2.10.0-python3.patch" +} + +build(){ + cd "${pkgbase}-${pkgver}" + ./configure --prefix=/usr --without-gimp + make +} + +for _lang in "${_languages[@]}" +do + _locale=${_lang%% *} + _pkgname=${pkgbase}-${_locale,,} + + pkgname+=(${_pkgname}) + eval "package_${_pkgname}() { + _package ${_lang} + }" +done Copied: gimp-help/repos/community-any/gimp-help-2.10.0-python3.patch (from rev 967605, gimp-help/trunk/gimp-help-2.10.0-python3.patch) =================================================================== --- gimp-help-2.10.0-python3.patch (rev 0) +++ gimp-help-2.10.0-python3.patch 2021-06-27 18:39:29 UTC (rev 967606) @@ -0,0 +1,975 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jacob Boerema <jgboer...@gmail.com> +Date: Thu, 4 Feb 2021 15:31:12 -0500 +Subject: [PATCH 1/3] tools: start migration to python3 by running 2to3 + +(cherry picked from commit c5079e972d62858eead44c9029d276d456daf90d) +--- + tools/xml2po.py | 14 ++++++------ + tools/xml2po/__init__.py | 40 +++++++++++++++++----------------- + tools/xml2po/modes/docbook.py | 20 ++++++++--------- + tools/xml2po/modes/gimphelp.py | 14 ++++++------ + 4 files changed, 44 insertions(+), 44 deletions(-) + +diff --git a/tools/xml2po.py b/tools/xml2po.py +index 037e61f27..b3e110dc3 100755 +--- a/tools/xml2po.py ++++ b/tools/xml2po.py +@@ -41,9 +41,9 @@ NULL_STRING = '/dev/null' + if not os.path.exists('/dev/null'): NULL_STRING = 'NUL' + + def usage (with_help = False): +- print >> sys.stderr, "Usage: %s [OPTIONS] [XMLFILE]..." % (sys.argv[0]) ++ print("Usage: %s [OPTIONS] [XMLFILE]..." % (sys.argv[0]), file=sys.stderr) + if with_help: +- print >> sys.stderr, """ ++ print(""" + OPTIONS may be some of: + -a --automatic-tags Automatically decides if tags are to be considered + "final" or not +@@ -72,7 +72,7 @@ EXAMPLES: + using -p option for each XML file: + %(command)s -p de.po chapter1.xml > chapter1.de.xml + %(command)s -p de.po chapter2.xml > chapter2.de.xml +-""" % {'command': sys.argv[0]} ++""" % {'command': sys.argv[0]}, file=sys.stderr) + + + def main(argv): +@@ -148,7 +148,7 @@ def main(argv): + sys.exit(0) + + if operation == 'update' and output != "-": +- print >> sys.stderr, "Option '-o' is not yet supported when updating translations directly. Ignoring this option." ++ print("Option '-o' is not yet supported when updating translations directly. Ignoring this option.", file=sys.stderr) + + # Treat remaining arguments as XML files + filenames = [] +@@ -158,16 +158,16 @@ def main(argv): + try: + xml2po_main = Main(default_mode, operation, output, options) + except IOError: +- print >> sys.stderr, "Error: cannot open file %s for writing." % (output) ++ print("Error: cannot open file %s for writing." % (output), file=sys.stderr) + sys.exit(5) + + if operation == 'merge': + if len(filenames) > 1: +- print >> sys.stderr, "Error: You can merge translations with only one XML file at a time." ++ print("Error: You can merge translations with only one XML file at a time.", file=sys.stderr) + sys.exit(2) + + if not mofile: +- print >> sys.stderr, "Error: You must specify MO file when merging translations." ++ print("Error: You must specify MO file when merging translations.", file=sys.stderr) + sys.exit(3) + + xml2po_main.merge(mofile, filenames[0]) +diff --git a/tools/xml2po/__init__.py b/tools/xml2po/__init__.py +index eec197947..7a07eb226 100644 +--- a/tools/xml2po/__init__.py ++++ b/tools/xml2po/__init__.py +@@ -86,14 +86,14 @@ class MessageOutput: + self.messages.append(t) + if spacepreserve: + self.nowrap[t] = True +- if t in self.linenos.keys(): ++ if t in list(self.linenos.keys()): + self.linenos[t].append((self.filename, tag, lineno)) + else: + self.linenos[t] = [ (self.filename, tag, lineno) ] + if (not self.do_translations) and comment and not t in self.comments: + self.comments[t] = comment + else: +- if t in self.linenos.keys(): ++ if t in list(self.linenos.keys()): + self.linenos[t].append((self.filename, tag, lineno)) + else: + self.linenos[t] = [ (self.filename, tag, lineno) ] +@@ -166,7 +166,7 @@ class XMLDocument(object): + elif node.isText(): + if node.isBlankNode(): + if self.app.options.get('expand_entities') or \ +- (not (node.prev and not node.prev.isBlankNode() and node.next and not node.next.isBlankNode()) ): ++ (not (node.prev and not node.prev.isBlankNode() and node.__next__ and not node.next.isBlankNode()) ): + #print >>sys.stderr, "BLANK" + node.setContent('') + else: +@@ -176,7 +176,7 @@ class XMLDocument(object): + child = node.children + while child: + self.normalizeNode(child) +- child = child.next ++ child = child.__next__ + + def normalizeString(self, text, spacepreserve = False): + """Normalizes string to be used as key for gettext lookup. +@@ -200,7 +200,7 @@ class XMLDocument(object): + tree = ctxt.doc() + newnode = tree.getRootElement() + except: +- print >> sys.stderr, """Error while normalizing string as XML:\n"%s"\n""" % (text) ++ print("""Error while normalizing string as XML:\n"%s"\n""" % (text), file=sys.stderr) + return text + + self.normalizeNode(newnode) +@@ -209,7 +209,7 @@ class XMLDocument(object): + child = newnode.children + while child: + result += child.serialize('utf-8') +- child = child.next ++ child = child.__next__ + + result = re.sub('^ ','', result) + result = re.sub(' $','', result) +@@ -235,7 +235,7 @@ class XMLDocument(object): + ctxt.parseDocument() + tree = ctxt.doc() + if next: +- newnode = tree.children.next ++ newnode = tree.children.__next__ + else: + newnode = tree.children + +@@ -243,7 +243,7 @@ class XMLDocument(object): + child = newnode.children + while child: + result += child.serialize('utf-8') +- child = child.next ++ child = child.__next__ + tree.freeDoc() + return result + +@@ -262,7 +262,7 @@ class XMLDocument(object): + result += child.content.decode('utf-8') + else: + result += self.myAttributeSerialize(child) +- child = child.next ++ child = child.__next__ + else: + result = node.serialize('utf-8') + return result +@@ -338,7 +338,7 @@ class XMLDocument(object): + pass + + if not newnode: +- print >> sys.stderr, """Error while parsing translation as XML:\n"%s"\n""" % (text.encode('utf-8')) ++ print("""Error while parsing translation as XML:\n"%s"\n""" % (text.encode('utf-8')), file=sys.stderr) + return + + newelem = newnode.getRootElement() +@@ -346,13 +346,13 @@ class XMLDocument(object): + if newelem and newelem.children: + free = node.children + while free: +- next = free.next ++ next = free.__next__ + free.unlinkNode() + free = next + + if node: + copy = newelem.copyNodeList() +- next = node.next ++ next = node.__next__ + node.replaceNode(newelem.copyNodeList()) + node.next = next + +@@ -377,7 +377,7 @@ class XMLDocument(object): + if child.isText() and child.content.strip() != '': + return True + else: +- child = child.next ++ child = child.__next__ + return False + + +@@ -441,7 +441,7 @@ class XMLDocument(object): + outtxt += '<%s>%s</%s>' % (starttag, content, endtag) + else: + outtxt += self.doSerialize(child) +- child = child.next ++ child = child.__next__ + + if self.app.operation == 'merge': + norm_outtxt = self.normalizeString(outtxt, self.app.isSpacePreserveNode(node)) +@@ -521,7 +521,7 @@ class XMLDocument(object): + outtxt = '' + while child: + outtxt += self.doSerialize(child) +- child = child.next ++ child = child.__next__ + return outtxt + + def xml_error_handler(arg, ctxt): +@@ -565,7 +565,7 @@ class Main(object): + try: + doc = XMLDocument(xmlfile, self) + except Exception as e: +- print >> sys.stderr, "Unable to parse XML file '%s': %s" % (xmlfile, str(e)) ++ print("Unable to parse XML file '%s': %s" % (xmlfile, str(e)), file=sys.stderr) + sys.exit(1) + self.current_mode.preProcessXml(doc.doc, self.msg) + doc.generate_messages() +@@ -578,13 +578,13 @@ class Main(object): + try: + doc = XMLDocument(xmlfile, self) + except Exception as e: +- print >> sys.stderr, str(e) ++ print(str(e), file=sys.stderr) + sys.exit(1) + + try: + mfile = open(mofile, "rb") + except: +- print >> sys.stderr, "Can't open MO file '%s'." % (mofile) ++ print("Can't open MO file '%s'." % (mofile), file=sys.stderr) + self.gt = gettext.GNUTranslations(mfile) + self.gt.add_fallback(NoneTranslations()) + # Has preProcessXml use cases for merge? +@@ -607,7 +607,7 @@ class Main(object): + try: + doc = XMLDocument(xmlfile, self) + except Exception as e: +- print >> sys.stderr, str(e) ++ print(str(e), file=sys.stderr) + sys.exit(1) + doc.generate_messages() + +@@ -615,7 +615,7 @@ class Main(object): + try: + doc = XMLDocument(origxml, self) + except Exception as e: +- print >> sys.stderr, str(e) ++ print(str(e), file=sys.stderr) + sys.exit(1) + doc.generate_messages() + self.output_po() +diff --git a/tools/xml2po/modes/docbook.py b/tools/xml2po/modes/docbook.py +index 276a9d9bb..5658ffc1a 100644 +--- a/tools/xml2po/modes/docbook.py ++++ b/tools/xml2po/modes/docbook.py +@@ -43,7 +43,7 @@ try: + except ImportError: + from md5 import new as md5_new + +-from basic import basicXmlMode ++from .basic import basicXmlMode + + class docbookXmlMode(basicXmlMode): + """Class for special handling of DocBook document types. +@@ -95,7 +95,7 @@ class docbookXmlMode(basicXmlMode): + ret = self._find_articleinfo(child) + if ret: + return ret +- child = child.next ++ child = child.__next__ + return None + + def _find_lastcopyright(self, node): +@@ -131,7 +131,7 @@ class docbookXmlMode(basicXmlMode): + hash = self._md5_for_file(fullpath) + else: + hash = "THIS FILE DOESN'T EXIST" +- print >>sys.stderr, "Warning: image file '%s' not found." % fullpath ++ print("Warning: image file '%s' not found." % fullpath, file=sys.stderr) + + msg.outputMessage("@@image: '%s'; md5=%s" % (attr, hash), node.lineNo(), + "When image changes, this message will be marked fuzzy or untranslated for you.\n"+ +@@ -140,7 +140,7 @@ class docbookXmlMode(basicXmlMode): + child = node.children + while child: + self._output_images(child,msg) +- child = child.next ++ child = child.__next__ + + + def preProcessXml(self, doc, msg): +@@ -157,7 +157,7 @@ class docbookXmlMode(basicXmlMode): + root = doc.getRootElement() + # DocBook documents can be something other than article, handle that as well in the future + while root and root.name != 'article' and root.name != 'book': +- root = root.next ++ root = root.__next__ + if root and (root.name == 'article' or root.name == 'book'): + root.setProp('lang', language) + else: +@@ -198,10 +198,10 @@ class docbookXmlMode(basicXmlMode): + # Perform some tests when ran standalone + if __name__ == '__main__': + test = docbookXmlMode() +- print "Ignored tags : " + repr(test.getIgnoredTags()) +- print "Final tags : " + repr(test.getFinalTags()) +- print "Space-preserve tags: " + repr(test.getSpacePreserveTags()) ++ print("Ignored tags : " + repr(test.getIgnoredTags())) ++ print("Final tags : " + repr(test.getFinalTags())) ++ print("Space-preserve tags: " + repr(test.getSpacePreserveTags())) + +- print "Credits from string: '%s'" % test.getStringForTranslators() +- print "Explanation for credits:\n\t'%s'" % test.getCommentForTranslators() ++ print("Credits from string: '%s'" % test.getStringForTranslators()) ++ print("Explanation for credits:\n\t'%s'" % test.getCommentForTranslators()) + +diff --git a/tools/xml2po/modes/gimphelp.py b/tools/xml2po/modes/gimphelp.py +index 24d4154e6..d72f518ee 100644 +--- a/tools/xml2po/modes/gimphelp.py ++++ b/tools/xml2po/modes/gimphelp.py +@@ -31,7 +31,7 @@ try: + except ImportError: + from md5 import new as md5_new + +-from docbook import docbookXmlMode ++from .docbook import docbookXmlMode + + class gimphelpXmlMode(docbookXmlMode): + """Class for special handling of gimp-help DocBook document types. +@@ -81,7 +81,7 @@ class gimphelpXmlMode(docbookXmlMode): + child = node.children + while child: + self._output_images(child,msg) +- child = child.next ++ child = child.__next__ + + def preProcessXml(self, doc, msg): + """Add additional messages of interest here.""" +@@ -91,10 +91,10 @@ class gimphelpXmlMode(docbookXmlMode): + # Perform some tests when ran standalone + if __name__ == '__main__': + test = gimphelpXmlMode() +- print "Ignored tags : " + repr(test.getIgnoredTags()) +- print "Final tags : " + repr(test.getFinalTags()) +- print "Space-preserve tags: " + repr(test.getSpacePreserveTags()) ++ print("Ignored tags : " + repr(test.getIgnoredTags())) ++ print("Final tags : " + repr(test.getFinalTags())) ++ print("Space-preserve tags: " + repr(test.getSpacePreserveTags())) + +- print "Credits from string: '%s'" % test.getStringForTranslators() +- print "Explanation for credits:\n\t'%s'" % test.getCommentForTranslators() ++ print("Credits from string: '%s'" % test.getStringForTranslators()) ++ print("Explanation for credits:\n\t'%s'" % test.getCommentForTranslators()) + +-- +2.29.2 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jacob Boerema <jgboer...@gmail.com> +Date: Mon, 29 Mar 2021 17:51:48 -0400 +Subject: [PATCH 2/3] tools: complete port of xml2po to python3 + +Also: +- Let it work correctly on Windows by setting encoding for + stdout and stderr to utf-8. +- Fix setting correct timezone in po files. +- Better error handling. +- Detect errors in tags in the translated xml text. + +(cherry picked from commit 299cfb7150857f0045ae42978d8ed917b4b0e4ff) +--- + tools/xml2po.py | 19 +++- + tools/xml2po/__init__.py | 189 +++++++++++++++++++++++++-------- + tools/xml2po/modes/gimphelp.py | 2 +- + 3 files changed, 163 insertions(+), 47 deletions(-) + +diff --git a/tools/xml2po.py b/tools/xml2po.py +index b3e110dc3..ef8d725da 100755 +--- a/tools/xml2po.py ++++ b/tools/xml2po.py +@@ -1,4 +1,4 @@ +-#!/usr/bin/env python2 ++#!/usr/bin/env python3 + # -*- encoding: utf-8 -*- + # Copyright (c) 2004, 2005, 2006 Danilo Šegan <dan...@gnome.org>. + # Copyright (c) 2009 Claude Paroz <cla...@2xlibre.net>. +@@ -21,7 +21,7 @@ + # + + # xml2po -- translate XML documents +-VERSION = "0.18.0 (patched by GIMP Documentation Team)" ++VERSION = "0.19.0 (patched by GIMP Documentation Team)" + + # Versioning system (I use this for a long time, so lets explain it to + # those Linux-versioning-scheme addicts): +@@ -37,11 +37,13 @@ import os + import getopt + import tempfile + ++DEBUG_VERBOSITY = 0 ++ + NULL_STRING = '/dev/null' + if not os.path.exists('/dev/null'): NULL_STRING = 'NUL' + + def usage (with_help = False): +- print("Usage: %s [OPTIONS] [XMLFILE]..." % (sys.argv[0]), file=sys.stderr) ++ print(f"Usage: {sys.argv[0]} [OPTIONS] [XMLFILE]...", file=sys.stderr) + if with_help: + print(""" + OPTIONS may be some of: +@@ -86,6 +88,11 @@ def main(argv): + + from xml2po import Main + ++ ++ # Make sure stdout and stderr output utf-8 even on Windows where it's not the default ++ sys.stdout = open(sys.stdout.fileno(), 'w', encoding='utf-8', closefd=False) ++ sys.stderr = open(sys.stderr.fileno(), 'w', encoding='utf-8', closefd=False) ++ + # Default parameters + default_mode = 'docbook' + operation = 'pot' # 'pot', 'merge', 'update' +@@ -136,6 +143,8 @@ def main(argv): + operation = 'merge' + if 'translationlanguage' not in options: + options['translationlanguage'] = os.path.split(os.path.splitext(pofile)[0])[1] ++ if DEBUG_VERBOSITY > 0: ++ print(f"Converting {pofile} to {mofile_tmppath} using msgfmt") + os.system("msgfmt -o %s %s >%s" % (mofile_tmppath, pofile, NULL_STRING)) and sys.exit(7) + mofile = mofile_tmppath + elif opt in ('-o', '--output'): +@@ -170,6 +179,10 @@ def main(argv): + print("Error: You must specify MO file when merging translations.", file=sys.stderr) + sys.exit(3) + ++ if DEBUG_VERBOSITY > 0: ++ print(f"Merge mo file {mofile} with {filenames[0]}") ++ if pofile: ++ xml2po_main.pofile = pofile + xml2po_main.merge(mofile, filenames[0]) + + elif operation == 'update': +diff --git a/tools/xml2po/__init__.py b/tools/xml2po/__init__.py +index 7a07eb226..42fa81f39 100644 +--- a/tools/xml2po/__init__.py ++++ b/tools/xml2po/__init__.py +@@ -26,6 +26,7 @@ import tempfile + import gettext + import libxml2 + ++DEBUG_VERBOSITY = 0 + NULL_STRING = '/dev/null' + if not os.path.exists('/dev/null'): NULL_STRING = 'NUL' + +@@ -86,14 +87,14 @@ class MessageOutput: + self.messages.append(t) + if spacepreserve: + self.nowrap[t] = True +- if t in list(self.linenos.keys()): ++ if t in self.linenos.keys(): + self.linenos[t].append((self.filename, tag, lineno)) + else: + self.linenos[t] = [ (self.filename, tag, lineno) ] + if (not self.do_translations) and comment and not t in self.comments: + self.comments[t] = comment + else: +- if t in list(self.linenos.keys()): ++ if t in self.linenos.keys(): + self.linenos[t].append((self.filename, tag, lineno)) + else: + self.linenos[t] = [ (self.filename, tag, lineno) ] +@@ -101,7 +102,11 @@ class MessageOutput: + self.comments[t] = comment + + def outputHeader(self, out): +- import time ++ from datetime import datetime ++ # Using time.strftime was not working correctly for me: instead of a ++ # timezone offset a timezone name was added. This fixes it. ++ dt = datetime.now() ++ tz = dt.astimezone().tzinfo + out.write("""msgid "" + msgstr "" + "Project-Id-Version: PACKAGE VERSION\\n" +@@ -113,7 +118,7 @@ msgstr "" + "Content-Type: text/plain; charset=UTF-8\\n" + "Content-Transfer-Encoding: 8bit\\n" + +-""" % (time.strftime("%Y-%m-%d %H:%M%z"))) ++""" % (dt.astimezone(tz).strftime("%Y-%m-%d %H:%M%z"))) + + def outputAll(self, out): + self.outputHeader(out) +@@ -138,6 +143,7 @@ msgstr "" + + class XMLDocument(object): + def __init__(self, filename, app): ++ self.filename = filename + self.app = app + self.expand_entities = self.app.options.get('expand_entities') + self.ignored_tags = self.app.current_mode.getIgnoredTags() +@@ -145,7 +151,13 @@ class XMLDocument(object): + ctxt.lineNumbers(1) + if self.app.options.get('expand_all_entities'): + ctxt.replaceEntities(1) +- ctxt.parseDocument() ++ ++ try: ++ ctxt.parseDocument() ++ except Exception as e: ++ print("Error parsing XML file '%s': %s" % (filename, str(e)), file=sys.stderr) ++ sys.exit(1) ++ + self.doc = ctxt.doc() + if self.doc.name != filename: + raise Exception("Error: I tried to open '%s' but got '%s' -- how did that happen?" % (filename, self.doc.name)) +@@ -166,22 +178,23 @@ class XMLDocument(object): + elif node.isText(): + if node.isBlankNode(): + if self.app.options.get('expand_entities') or \ +- (not (node.prev and not node.prev.isBlankNode() and node.__next__ and not node.next.isBlankNode()) ): +- #print >>sys.stderr, "BLANK" ++ (not (node.prev and not node.prev.isBlankNode() and node.next and not node.next.isBlankNode()) ): + node.setContent('') + else: +- node.setContent(re.sub('\s+',' ', node.content)) ++ node.setContent(re.sub(r'\s+',' ', node.content)) + + elif node.children and node.type == 'element': + child = node.children + while child: ++ nextchild = child.next + self.normalizeNode(child) +- child = child.__next__ ++ child = nextchild + + def normalizeString(self, text, spacepreserve = False): + """Normalizes string to be used as key for gettext lookup. + + Removes all unnecessary whitespace.""" ++ mytext = text + if spacepreserve: + return text + try: +@@ -203,13 +216,20 @@ class XMLDocument(object): + print("""Error while normalizing string as XML:\n"%s"\n""" % (text), file=sys.stderr) + return text + ++ # Not sure if saving the doc here is really necessary. It was one of the ++ # things done in debugging and don't want to spend time now to check if ++ # we can remove it. ++ save_doc = self.doc ++ self.doc = ctxt.doc() + self.normalizeNode(newnode) ++ self.doc = save_doc + + result = '' + child = newnode.children + while child: ++ nextchild = child.next + result += child.serialize('utf-8') +- child = child.__next__ ++ child = nextchild + + result = re.sub('^ ','', result) + result = re.sub(' $','', result) +@@ -235,15 +255,16 @@ class XMLDocument(object): + ctxt.parseDocument() + tree = ctxt.doc() + if next: +- newnode = tree.children.__next__ ++ newnode = tree.children.next + else: + newnode = tree.children + + result = '' + child = newnode.children + while child: ++ nextchild = child.next + result += child.serialize('utf-8') +- child = child.__next__ ++ child = nextchild + tree.freeDoc() + return result + +@@ -252,6 +273,7 @@ class XMLDocument(object): + result = '' + if node.children: + child = node.children ++ nextchild = child.next + while child: + if child.type=='text': + result += self.doc.encodeEntitiesReentrant(child.content) +@@ -262,7 +284,7 @@ class XMLDocument(object): + result += child.content.decode('utf-8') + else: + result += self.myAttributeSerialize(child) +- child = child.__next__ ++ child = nextchild + else: + result = node.serialize('utf-8') + return result +@@ -308,11 +330,81 @@ class XMLDocument(object): + return None + + def replaceAttributeContentsWithText(self, node, text): +- node.setContent(text) ++ try: ++ node.setContent(text.decode('utf-8')) ++ except TypeError: ++ sys.stderr.write("--> replaceAttributeContentsWithText: Failed to decode text to utf-8.") ++ sys.exit(1) ++ ++ def CheckMatchedTags(self, text): ++ stack = [] ++ textblock = text ++ ++ log=sys.stdout ++ ++ # It might be even better to do the below with regex, see e.g. ++ # https://datadependence.com/2016/03/find-unclosed-tags-using-stacks/ ++ # However I'm not sure it really matters that much since the text ++ # blocks usually are fairly small and most don't have a lot of tags. ++ start_tag = textblock.find('<') ++ while start_tag > -1: ++ textblock = textblock[start_tag+1:] ++ end_tag = textblock.find('>') ++ if end_tag > -1: ++ # Found left and right brackets: grab tag ++ tag = textblock[: end_tag] ++ # Check that it's not a tag that closes itself and comment tags starting with <! ++ if textblock[end_tag-1] != '/' and textblock[0] != '!': ++ # Tag can have multiple elements inside, watch for first space ++ space = tag.find(' ') ++ if space > -1: ++ tag = tag[: space] ++ ++ open_tag = (len(tag) > 0 and tag[0] != '/') ++ if open_tag: ++ # Add tag to stack ++ stack.append(tag) ++ else: ++ tag = tag[1:] ++ if len(stack) == 0: ++ pass ++ else: ++ if stack[-1] == tag: ++ # Close the block ++ stack.pop() ++ else: ++ print(f"\n========================", file=log) ++ print(f"Source xml: {self.filename}", file=log) ++ print(f"Source po : {self.app.pofile}", file=log) ++ print(f"Translated msgstr:\n{text}\n", file=log) ++ print(f"WARNING: Found closing tag [{tag}], however we expected [{stack[0]}].", file=log) ++ print(f"Remaining tags: {str(stack)}", file=log) ++ if tag in stack: ++ stack.remove(tag) ++ print(" Assuming incorrect tag order, found and removed tag from the stack", file=log) ++ print(f"========================\n", file=log) ++ textblock = textblock[end_tag+1:] ++ start_tag = textblock.find('<') ++ else: ++ start_tag = -1 ++ ++ ++ if len(stack): ++ print(f"\n========================", file=log) ++ print(f"Source xml: {self.filename}", file=log) ++ print(f"Source po : {self.app.pofile}", file=log) ++ print(f"ERROR: Found unmatched tags in po msgstr:\n{text}\n", file=log) ++ print(f"Tags not matched: {str(stack)}", file=log) ++ print(f"========================\n", file=log) ++ return False ++ return True + + def replaceNodeContentsWithText(self, node, text): + """Replaces all subnodes of a node with contents of text treated as XML.""" + ++ if not self.CheckMatchedTags(text): ++ return ++ + if node.children: + starttag = self.startTagForNode(node) + endtag = self.endTagForNode(node) +@@ -326,7 +418,7 @@ class XMLDocument(object): + pass + + content = '<%s>%s</%s>' % (starttag, text, endtag) +- tmp = tmp + content.encode('utf-8') ++ tmp = tmp + content + + newnode = None + try: +@@ -338,7 +430,9 @@ class XMLDocument(object): + pass + + if not newnode: +- print("""Error while parsing translation as XML:\n"%s"\n""" % (text.encode('utf-8')), file=sys.stderr) ++ print(f"\n--> Error parsing translation as XML:\n{text}") ++ # See: https://gitlab.gnome.org/GNOME/libxml2/-/issues/64 ++ print("--> Note: this might be caused by a bug in libxml2.\n") + return + + newelem = newnode.getRootElement() +@@ -346,15 +440,14 @@ class XMLDocument(object): + if newelem and newelem.children: + free = node.children + while free: +- next = free.__next__ ++ nextchild = free.next + free.unlinkNode() +- free = next ++ free = nextchild + + if node: +- copy = newelem.copyNodeList() +- next = node.__next__ ++ nextnode = node.next + node.replaceNode(newelem.copyNodeList()) +- node.next = next ++ node.__next__ = nextnode + + else: + # In practice, this happens with tags such as "<para> </para>" (only whitespace in between) +@@ -374,10 +467,11 @@ class XMLDocument(object): + return True + child = node.children + while child: ++ nextchild = child.next + if child.isText() and child.content.strip() != '': + return True + else: +- child = child.__next__ ++ child = nextchild + return False + + +@@ -432,6 +526,10 @@ class XMLDocument(object): + + child = node.children + while child: ++ # Although I do not know why, child or child.next gets changed inside the if part below. ++ # This makes child.next fail when it shouldn't. That's why we store nextchild here ++ # before going into the if and use that at the end of the loop ++ nextchild = child.next + if (self.isFinalNode(child)) or (child.type == 'element' and self.worthOutputting(child)): + myrepl.append(self.processElementTag(child, myrepl, True)) + outtxt += '<placeholder-%d/>' % (len(myrepl)) +@@ -441,20 +539,20 @@ class XMLDocument(object): + outtxt += '<%s>%s</%s>' % (starttag, content, endtag) + else: + outtxt += self.doSerialize(child) +- child = child.__next__ ++ child = nextchild + + if self.app.operation == 'merge': + norm_outtxt = self.normalizeString(outtxt, self.app.isSpacePreserveNode(node)) + translation = self.app.getTranslation(norm_outtxt) + else: +- translation = outtxt.decode('utf-8') ++ translation = outtxt + + starttag = self.startTagForNode(node) + endtag = self.endTagForNode(node) + + worth = self.worthOutputting(node) + if not translation: +- translation = outtxt.decode('utf-8') ++ translation = outtxt + if worth and self.app.options.get('mark_untranslated'): + node.setLang('C') + +@@ -463,7 +561,7 @@ class XMLDocument(object): + # repl[0] may contain translated attributes with + # non-ASCII chars, so implicit conversion to <str> may fail + replacement = '<%s>%s</%s>' % \ +- (repl[0].decode('utf-8'), repl[3], repl[2]) ++ (repl[0], repl[3], repl[2]) + translation = translation.replace('<placeholder-%d/>' % (i+1), replacement) + + if worth: +@@ -479,7 +577,7 @@ class XMLDocument(object): + def isExternalGeneralParsedEntity(self, node): + try: + # it would be nice if debugDumpNode could use StringIO, but it apparently cannot +- tmp = tempfile.TemporaryFile() ++ tmp = tempfile.TemporaryFile(encoding='utf-8') + node.debugDumpNode(tmp,0) + tmp.seek(0) + tmpstr = tmp.read() +@@ -507,25 +605,31 @@ class XMLDocument(object): + if self.isExternalGeneralParsedEntity(node): + return node.serialize('utf-8') + else: +- return self.stringForEntity(node) #content #content #serialize("utf-8") +- elif node.type == 'entity_decl': ++ return self.stringForEntity(node) ++ elif node.type == 'entity_decl --> serialize': + return node.serialize('utf-8') #'<%s>%s</%s>' % (startTagForNode(node), node.content, node.name) + elif node.type == 'text': +- return node.serialize('utf-8') ++ nodetext = node.serialize('utf-8') ++ return nodetext + elif node.type == 'element': + repl = [] + (starttag, content, endtag, translation) = self.processElementTag(node, repl, True) +- return '<%s>%s</%s>' % (starttag, content, endtag) ++ return '<%s>%s</%s>' % (starttag, content.encode('utf-8'), endtag) + else: + child = node.children + outtxt = '' + while child: ++ # Not sure if the same problem with using next.child happens here too ++ # but we will use nextchild here too just to be sure ++ nextchild = child.next + outtxt += self.doSerialize(child) +- child = child.__next__ ++ child = nextchild + return outtxt + +-def xml_error_handler(arg, ctxt): ++def xml_error_handler(ctxt, error): + #deactivate error messages from the validation ++ if DEBUG_VERBOSITY > 0: ++ print(f"--> xml_error_handler: {error}") + pass + + class Main(object): +@@ -538,11 +642,11 @@ class Main(object): + self.current_mode = self.load_mode(mode)() + # Prepare output + if operation == 'update': +- self.out = tempfile.TemporaryFile() ++ self.out = tempfile.TemporaryFile(encoding='utf-8') + elif output == '-': + self.out = sys.stdout + else: +- self.out = file(output, 'w') ++ self.out = open(output, 'w', encoding='utf-8', buffering=1) + + def load_mode(self, modename): + try: +@@ -565,7 +669,7 @@ class Main(object): + try: + doc = XMLDocument(xmlfile, self) + except Exception as e: +- print("Unable to parse XML file '%s': %s" % (xmlfile, str(e)), file=sys.stderr) ++ print("Error parsing XML file '%s': %s" % (xmlfile, str(e)), file=sys.stderr) + sys.exit(1) + self.current_mode.preProcessXml(doc.doc, self.msg) + doc.generate_messages() +@@ -578,13 +682,13 @@ class Main(object): + try: + doc = XMLDocument(xmlfile, self) + except Exception as e: +- print(str(e), file=sys.stderr) ++ print("Error parsing XML file '%s': %s" % (xmlfile, str(e)), file=sys.stderr) + sys.exit(1) +- + try: + mfile = open(mofile, "rb") + except: +- print("Can't open MO file '%s'." % (mofile), file=sys.stderr) ++ print("Error opening MO file '%s': %s." % (mofile, str(e)), file=sys.stderr) ++ sys.exit(1) + self.gt = gettext.GNUTranslations(mfile) + self.gt.add_fallback(NoneTranslations()) + # Has preProcessXml use cases for merge? +@@ -607,7 +711,7 @@ class Main(object): + try: + doc = XMLDocument(xmlfile, self) + except Exception as e: +- print(str(e), file=sys.stderr) ++ print("Error parsing XML file '%s': %s" % (xmlfile, str(e)), file=sys.stderr) + sys.exit(1) + doc.generate_messages() + +@@ -615,7 +719,7 @@ class Main(object): + try: + doc = XMLDocument(origxml, self) + except Exception as e: +- print(str(e), file=sys.stderr) ++ print("Error parsing XML file '%s': %s" % (origxml, str(e)), file=sys.stderr) + sys.exit(1) + doc.generate_messages() + self.output_po() +@@ -646,11 +750,10 @@ class Main(object): + + text should be a string to look for. + """ +- #print >>sys.stderr,"getTranslation('%s')" % (text.encode('utf-8')) + if not text or text.strip() == '': + return text + if self.gt: +- res = self.gt.ugettext(text.decode('utf-8')) ++ res = self.gt.gettext(text) + return res + + return text +diff --git a/tools/xml2po/modes/gimphelp.py b/tools/xml2po/modes/gimphelp.py +index d72f518ee..0e7e31c73 100644 +--- a/tools/xml2po/modes/gimphelp.py ++++ b/tools/xml2po/modes/gimphelp.py +@@ -81,7 +81,7 @@ class gimphelpXmlMode(docbookXmlMode): + child = node.children + while child: + self._output_images(child,msg) +- child = child.__next__ ++ child = child.next + + def preProcessXml(self, doc, msg): + """Add additional messages of interest here.""" +-- +2.29.2 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jacob Boerema <jgboer...@gmail.com> +Date: Mon, 29 Mar 2021 17:55:35 -0400 +Subject: [PATCH 3/3] tools: also update docbook.py to python3. + +(cherry picked from commit b2d8952713245cbec4fef5b613b984549ca2b2fd) +--- + tools/xml2po/modes/docbook.py | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/tools/xml2po/modes/docbook.py b/tools/xml2po/modes/docbook.py +index 5658ffc1a..ed395080e 100644 +--- a/tools/xml2po/modes/docbook.py ++++ b/tools/xml2po/modes/docbook.py +@@ -95,7 +95,7 @@ class docbookXmlMode(basicXmlMode): + ret = self._find_articleinfo(child) + if ret: + return ret +- child = child.__next__ ++ child = child.next + return None + + def _find_lastcopyright(self, node): +@@ -140,7 +140,7 @@ class docbookXmlMode(basicXmlMode): + child = node.children + while child: + self._output_images(child,msg) +- child = child.__next__ ++ child = child.next + + + def preProcessXml(self, doc, msg): +@@ -157,7 +157,7 @@ class docbookXmlMode(basicXmlMode): + root = doc.getRootElement() + # DocBook documents can be something other than article, handle that as well in the future + while root and root.name != 'article' and root.name != 'book': +- root = root.__next__ ++ root = root.next + if root and (root.name == 'article' or root.name == 'book'): + root.setProp('lang', language) + else: +@@ -184,7 +184,7 @@ class docbookXmlMode(basicXmlMode): + else: + ai.addChild(copy) + if match.group(3): +- copy.newChild(None, "year", match.group(3).encode('utf-8')) ++ copy.newChild(None, "year", match.group(3)) + if match.group(1) and match.group(2): + holder = match.group(1)+"(%s)" % match.group(2) + elif match.group(1): +@@ -193,7 +193,7 @@ class docbookXmlMode(basicXmlMode): + holder = match.group(2) + else: + holder = "???" +- copy.newChild(None, "holder", holder.encode('utf-8')) ++ copy.newChild(None, "holder", holder) + + # Perform some tests when ran standalone + if __name__ == '__main__': +-- +2.31.1 +