From 7f29d5c4a599a7d18801fe4b5dcddfebeecf9461 Mon Sep 17 00:00:00 2001 From: Dan Callaghan Date: Thu, 5 Jul 2018 15:57:59 +1000 Subject: [PATCH] Upstream release 2.18, Python 3 support --- ....patch => 0001-Patch-for-RHBZ-750694.patch | 22 +++- ...-fix-imports-and-syntax-for-Python-3.patch | 123 ++++++++++++++++++ ...-to-return-str-not-bytes-on-Python-3.patch | 36 +++++ ...-is-slice-None-None-None-on-Python-3.patch | 27 ++++ ...edDict-for-attributes-and-namespaces.patch | 91 +++++++++++++ python-xmltramp.spec | 46 +++++-- xmltramp-2.17.py => xmltramp-2.18.py | 6 +- 7 files changed, 332 insertions(+), 19 deletions(-) rename python-xmltramp-2.17-nsprefixes.patch => 0001-Patch-for-RHBZ-750694.patch (56%) create mode 100644 0002-fix-imports-and-syntax-for-Python-3.patch create mode 100644 0003-__str__-needs-to-return-str-not-bytes-on-Python-3.patch create mode 100644 0004-empty-slice-is-slice-None-None-None-on-Python-3.patch create mode 100644 0005-use-OrderedDict-for-attributes-and-namespaces.patch rename xmltramp-2.17.py => xmltramp-2.18.py (99%) diff --git a/python-xmltramp-2.17-nsprefixes.patch b/0001-Patch-for-RHBZ-750694.patch similarity index 56% rename from python-xmltramp-2.17-nsprefixes.patch rename to 0001-Patch-for-RHBZ-750694.patch index 6240f11..65c3561 100644 --- a/python-xmltramp-2.17-nsprefixes.patch +++ b/0001-Patch-for-RHBZ-750694.patch @@ -1,6 +1,17 @@ ---- a/xmltramp.py 2011-01-07 15:27:58.000000000 +1000 -+++ b/xmltramp.py 2011-11-02 14:03:07.881107507 +1000 -@@ -224,7 +224,9 @@ +From ebc1a41118befdc7a92e0ccd04819d488775316d Mon Sep 17 00:00:00 2001 +From: Dan Callaghan +Date: Fri, 18 Nov 2011 15:13:59 +1000 +Subject: [PATCH 1/5] Patch for RHBZ#750694 + +--- + xmltramp.py | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/xmltramp.py b/xmltramp.py +index 9a4908a..7819b25 100644 +--- a/xmltramp.py ++++ b/xmltramp.py +@@ -224,7 +224,9 @@ def startElementNS(self, name, qname, attrs): attrs = dict(attrs) newprefixes = {} @@ -11,10 +22,13 @@ self.stack.append(Element(name, attrs, prefixes=newprefixes.copy())) -@@ -357,5 +359,6 @@ +@@ -357,5 +359,6 @@ def unittest(): assert parse('').__repr__(1) == '' assert parse('').__repr__(1) == '' + assert parse('').__repr__(1) == '' if __name__ == '__main__': unittest() +-- +2.14.4 + diff --git a/0002-fix-imports-and-syntax-for-Python-3.patch b/0002-fix-imports-and-syntax-for-Python-3.patch new file mode 100644 index 0000000..91265de --- /dev/null +++ b/0002-fix-imports-and-syntax-for-Python-3.patch @@ -0,0 +1,123 @@ +From cc474a11cda8bae8f26f651a9d11e63209427184 Mon Sep 17 00:00:00 2001 +From: Dan Callaghan +Date: Thu, 5 Jul 2018 15:41:51 +1000 +Subject: [PATCH 2/5] fix imports and syntax for Python 3 + +--- + xmltramp.py | 36 +++++++++++++++++++++++------------- + 1 file changed, 23 insertions(+), 13 deletions(-) + +diff --git a/xmltramp.py b/xmltramp.py +index 7819b25..a3a188f 100644 +--- a/xmltramp.py ++++ b/xmltramp.py +@@ -5,7 +5,11 @@ + __credits__ = "Many thanks to pjz, bitsko, and DanC." + __copyright__ = "(C) 2003-2006 Aaron Swartz. GNU GPL 2." + +-if not hasattr(__builtins__, 'True'): True, False = 1, 0 ++try: ++ text_type = unicode ++except NameError: # PY3 ++ text_type = str ++ + def isstr(f): return isinstance(f, type('')) or isinstance(f, type(u'')) + def islst(f): return isinstance(f, type(())) or isinstance(f, type([])) + +@@ -87,7 +91,7 @@ def arep(a, inprefixes, addns=1): + elif isinstance(x, Element): + out += x.__repr__(recursive+1, multiline, inprefixes.copy()) + else: +- raise TypeError, "I wasn't expecting "+`x`+"." ++ raise TypeError("I wasn't expecting "+repr(x)+".") + if multiline and content: out += '\n' + ('\t' * (recursive-1)) + else: + if self._dir: out += '...' +@@ -99,25 +103,25 @@ def arep(a, inprefixes, addns=1): + def __unicode__(self): + text = '' + for x in self._dir: +- text += unicode(x) ++ text += text_type(x) + return ' '.join(text.split()) + + def __str__(self): + return self.__unicode__().encode('utf-8') + + def __getattr__(self, n): +- if n[0] == '_': raise AttributeError, "Use foo['"+n+"'] to access the child element." ++ if n[0] == '_': raise AttributeError("Use foo['"+n+"'] to access the child element.") + if self._dNS: n = (self._dNS, n) + for x in self._dir: + if isinstance(x, Element) and x._name == n: return x +- raise AttributeError, 'No child element named %s' % repr(n) ++ raise AttributeError('No child element named %s' % repr(n)) + + def __hasattr__(self, n): + for x in self._dir: + if isinstance(x, Element) and x._name == n: return True + return False + +- def __setattr__(self, n, v): ++ def __setattr__(self, n, v): + if n[0] == '_': self.__dict__[n] = v + else: self[n] = v + +@@ -140,7 +144,7 @@ def __getitem__(self, n): + if self._dNS and not islst(n): n = (self._dNS, n) + for x in self._dir: + if isinstance(x, Element) and x._name == n: return x +- raise KeyError, n ++ raise KeyError(n) + + def __setitem__(self, n, v): + if isinstance(n, type(0)): # d[1] +@@ -213,7 +217,7 @@ def __init__(self): + ContentHandler.__init__(self) + + def startPrefixMapping(self, prefix, uri): +- if not self.prefixes.has_key(prefix): self.prefixes[prefix] = [] ++ if prefix not in self.prefixes: self.prefixes[prefix] = [] + self.prefixes[prefix].append(uri) + def endPrefixMapping(self, prefix): + self.prefixes[prefix].pop() +@@ -255,12 +259,18 @@ def seed(fileobj): + return seeder.result + + def parse(text): +- from StringIO import StringIO ++ try: ++ from StringIO import StringIO ++ except ImportError: # PY3 ++ from io import StringIO + return seed(StringIO(text)) + + def load(url): +- import urllib +- return seed(urllib.urlopen(url)) ++ try: ++ from urllib.request import urlopen ++ except ImportError: # PY2 ++ from urllib import urlopen ++ return seed(urlopen(url)) + + def unittest(): + parse('afoobara').__repr__(1,1) == \ +@@ -276,12 +286,12 @@ def unittest(): + + try: + d._doesnotexist +- raise "ExpectedError", "but found success. Damn." ++ raise AssertionError("Expected error, but found success. Damn.") + except AttributeError: pass + assert d.bar._name == 'bar' + try: + d.doesnotexist +- raise "ExpectedError", "but found success. Damn." ++ raise AssertionError("Expected error, but found success. Damn.") + except AttributeError: pass + + assert hasattr(d, 'bar') == True +-- +2.14.4 + diff --git a/0003-__str__-needs-to-return-str-not-bytes-on-Python-3.patch b/0003-__str__-needs-to-return-str-not-bytes-on-Python-3.patch new file mode 100644 index 0000000..f178f93 --- /dev/null +++ b/0003-__str__-needs-to-return-str-not-bytes-on-Python-3.patch @@ -0,0 +1,36 @@ +From 3dd8fe4955d28e8dee1c890d487288163a760419 Mon Sep 17 00:00:00 2001 +From: Dan Callaghan +Date: Thu, 5 Jul 2018 15:42:10 +1000 +Subject: [PATCH 3/5] __str__ needs to return str not bytes on Python 3 + +--- + xmltramp.py | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/xmltramp.py b/xmltramp.py +index a3a188f..e2522f2 100644 +--- a/xmltramp.py ++++ b/xmltramp.py +@@ -5,6 +5,7 @@ + __credits__ = "Many thanks to pjz, bitsko, and DanC." + __copyright__ = "(C) 2003-2006 Aaron Swartz. GNU GPL 2." + ++import sys + try: + text_type = unicode + except NameError: # PY3 +@@ -107,7 +108,10 @@ def __unicode__(self): + return ' '.join(text.split()) + + def __str__(self): +- return self.__unicode__().encode('utf-8') ++ if sys.version_info[0] > 2: ++ return self.__unicode__() ++ else: ++ return self.__unicode__().encode('utf-8') + + def __getattr__(self, n): + if n[0] == '_': raise AttributeError("Use foo['"+n+"'] to access the child element.") +-- +2.14.4 + diff --git a/0004-empty-slice-is-slice-None-None-None-on-Python-3.patch b/0004-empty-slice-is-slice-None-None-None-on-Python-3.patch new file mode 100644 index 0000000..9f0ba6e --- /dev/null +++ b/0004-empty-slice-is-slice-None-None-None-on-Python-3.patch @@ -0,0 +1,27 @@ +From b9aebcfc971d55fa6f2bf0e895e34a19994b6fd3 Mon Sep 17 00:00:00 2001 +From: Dan Callaghan +Date: Thu, 5 Jul 2018 15:42:35 +1000 +Subject: [PATCH 4/5] empty slice is slice(None, None, None) on Python 3 + +On Python 2, the empty slice is slice(0, 9223372036854775807, None) so +this extra conditional was not necessary. +--- + xmltramp.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/xmltramp.py b/xmltramp.py +index e2522f2..0653112 100644 +--- a/xmltramp.py ++++ b/xmltramp.py +@@ -135,7 +135,7 @@ def __getitem__(self, n): + return self._dir[n] + elif isinstance(n, slice(0).__class__): + # numerical slices +- if isinstance(n.start, type(0)): return self._dir[n.start:n.stop] ++ if n.start is None or isinstance(n.start, type(0)): return self._dir[n.start:n.stop] + + # d['foo':] == all s + n = n.start +-- +2.14.4 + diff --git a/0005-use-OrderedDict-for-attributes-and-namespaces.patch b/0005-use-OrderedDict-for-attributes-and-namespaces.patch new file mode 100644 index 0000000..55f1562 --- /dev/null +++ b/0005-use-OrderedDict-for-attributes-and-namespaces.patch @@ -0,0 +1,91 @@ +From 1303e9a3797c48b1681532c7c7e2d70a64907258 Mon Sep 17 00:00:00 2001 +From: Dan Callaghan +Date: Thu, 5 Jul 2018 15:46:58 +1000 +Subject: [PATCH 5/5] use OrderedDict for attributes and namespaces + +This is to ensure consistent iteration order, mainly so that the tests +will work regardless of dict hashing behaviour which varies across +Python 2 and 3. It should be nicer for callers as well. +--- + xmltramp.py | 22 +++++++++++++--------- + 1 file changed, 13 insertions(+), 9 deletions(-) + +diff --git a/xmltramp.py b/xmltramp.py +index 0653112..d3f1541 100644 +--- a/xmltramp.py ++++ b/xmltramp.py +@@ -10,6 +10,10 @@ + text_type = unicode + except NameError: # PY3 + text_type = str ++try: ++ from collections import OrderedDict ++except ImportError: # PY<=2.6 ++ OrderedDict = dict + + def isstr(f): return isinstance(f, type('')) or isinstance(f, type(u'')) + def islst(f): return isinstance(f, type(())) or isinstance(f, type([])) +@@ -26,18 +30,18 @@ class Element: + def __init__(self, name, attrs=None, children=None, prefixes=None): + if islst(name) and name[0] == None: name = name[1] + if attrs: +- na = {} ++ na = OrderedDict() + for k in attrs.keys(): + if islst(k) and k[0] == None: na[k[1]] = attrs[k] + else: na[k] = attrs[k] + attrs = na + + self._name = name +- self._attrs = attrs or {} ++ self._attrs = attrs or OrderedDict() + self._dir = children or [] + +- prefixes = prefixes or {} +- self._prefixes = dict(zip(prefixes.values(), prefixes.keys())) ++ prefixes = prefixes or OrderedDict() ++ self._prefixes = OrderedDict(zip(prefixes.values(), prefixes.keys())) + + if prefixes: self._dNS = prefixes.get(None, None) + else: self._dNS = None +@@ -67,7 +71,7 @@ def arep(a, inprefixes, addns=1): + + return out + +- inprefixes = inprefixes or {u'http://www.w3.org/XML/1998/namespace':'xml'} ++ inprefixes = inprefixes or OrderedDict({u'http://www.w3.org/XML/1998/namespace':'xml'}) + + # need to call first to set inprefixes: + attributes = arep(self._attrs, inprefixes, recursive) +@@ -217,7 +221,7 @@ class Seeder(EntityResolver, DTDHandler, ContentHandler, ErrorHandler): + def __init__(self): + self.stack = [] + self.ch = '' +- self.prefixes = {} ++ self.prefixes = OrderedDict() + ContentHandler.__init__(self) + + def startPrefixMapping(self, prefix, uri): +@@ -231,7 +235,7 @@ def startElementNS(self, name, qname, attrs): + if ch and not ch.isspace(): self.stack[-1]._dir.append(ch) + + attrs = dict(attrs) +- newprefixes = {} ++ newprefixes = OrderedDict() + for k in self.prefixes.keys(): + if self.prefixes[k]: + newprefixes[k] = self.prefixes[k][-1] +@@ -333,8 +337,8 @@ def unittest(): + """) + + assert repr(d) == '...' +- assert d.__repr__(1) == 'John Polk and John PalfreyJohn PolkJohn PalfreyBuffy' +- assert d.__repr__(1,1) == '\n\tJohn Polk and John Palfrey\n\tJohn Polk\n\tJohn Palfrey\n\tBuffy\n' ++ assert d.__repr__(1) == 'John Polk and John PalfreyJohn PolkJohn PalfreyBuffy' ++ assert d.__repr__(1,1) == '\n\tJohn Polk and John Palfrey\n\tJohn Polk\n\tJohn Palfrey\n\tBuffy\n' + + assert repr(parse("")) == '' + +-- +2.14.4 + diff --git a/python-xmltramp.spec b/python-xmltramp.spec index 3d67d46..35c425a 100644 --- a/python-xmltramp.spec +++ b/python-xmltramp.spec @@ -1,8 +1,7 @@ -%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: python-xmltramp -Version: 2.17 -Release: 21%{?dist} +Version: 2.18 +Release: 1%{?dist} Summary: Pythonic API for XML Group: Development/Languages @@ -12,10 +11,15 @@ License: GPLv2 URL: http://www.aaronsw.com/2002/xmltramp/ Source0: http://www.aaronsw.com/2002/xmltramp/xmltramp-%{version}.py -Patch0: %{name}-%{version}-nsprefixes.patch +Patch1: 0001-Patch-for-RHBZ-750694.patch +Patch2: 0002-fix-imports-and-syntax-for-Python-3.patch +Patch3: 0003-__str__-needs-to-return-str-not-bytes-on-Python-3.patch +Patch4: 0004-empty-slice-is-slice-None-None-None-on-Python-3.patch +Patch5: 0005-use-OrderedDict-for-attributes-and-namespaces.patch BuildArch: noarch BuildRequires: python2-devel +BuildRequires: python3-devel %global _description\ xmltramp is a simple Pythonic API for working with XML @@ -28,30 +32,48 @@ Summary: %summary %description -n python2-xmltramp %_description +%package -n python3-xmltramp +Summary: %summary +%{?python_provide:%python_provide python3-xmltramp} + +%description -n python3-xmltramp %_description + %prep %setup -c -T cp -p %{SOURCE0} xmltramp.py -%patch0 -p1 -b.nsprefixes +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 %build # noarch %check -%{__python} xmltramp.py +%{__python2} xmltramp.py +%{__python3} xmltramp.py %install rm -rf $RPM_BUILD_ROOT -mkdir -p $RPM_BUILD_ROOT/%{python_sitelib}/ -cp -p xmltramp.py $RPM_BUILD_ROOT/%{python_sitelib}/ - - +mkdir -p $RPM_BUILD_ROOT/%{python2_sitelib}/ +cp -p xmltramp.py $RPM_BUILD_ROOT/%{python2_sitelib}/ +mkdir -p $RPM_BUILD_ROOT/%{python3_sitelib}/ +cp -p xmltramp.py $RPM_BUILD_ROOT/%{python3_sitelib}/ %files -n python2-xmltramp -%defattr(-,root,root,-) -%{python_sitelib}/xmltramp.py* +%{python2_sitelib}/xmltramp.py* + +%files -n python3-xmltramp +%{python3_sitelib}/xmltramp.py* +%{python3_sitelib}/__pycache__/ %changelog +* Thu Jul 05 2018 Dan Callaghan - 2.18-1 +- Updated to upstream release 2.18 (only ~10 years late...) +- Minimal fixes to support Python 3 + * Fri Feb 09 2018 Fedora Release Engineering - 2.17-21 - Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild diff --git a/xmltramp-2.17.py b/xmltramp-2.18.py similarity index 99% rename from xmltramp-2.17.py rename to xmltramp-2.18.py index 25dd0d0..9a4908a 100644 --- a/xmltramp-2.17.py +++ b/xmltramp-2.18.py @@ -1,6 +1,6 @@ """xmltramp: Make XML documents easily accessible.""" -__version__ = "2.17" +__version__ = "2.18" __author__ = "Aaron Swartz" __credits__ = "Many thanks to pjz, bitsko, and DanC." __copyright__ = "(C) 2003-2006 Aaron Swartz. GNU GPL 2." @@ -140,7 +140,7 @@ class Element: if self._dNS and not islst(n): n = (self._dNS, n) for x in self._dir: if isinstance(x, Element) and x._name == n: return x - raise KeyError + raise KeyError, n def __setitem__(self, n, v): if isinstance(n, type(0)): # d[1] @@ -191,7 +191,7 @@ class Element: if len(_pos) > 1: for i in range(0, len(_pos), 2): self._attrs[_pos[i]] = _pos[i+1] - if len(_pos) == 1 is not None: + if len(_pos) == 1: return self._attrs[_pos[0]] if len(_pos) == 0: return self._attrs