[Python-modules-commits] [python-odf] 01/01: New upstream version 1.3.6

Georges Khaznadar georgesk at moszumanska.debian.org
Sat Jan 13 16:49:36 UTC 2018


This is an automated email from the git hooks/post-receive script.

georgesk pushed a commit to branch upstream
in repository python-odf.

commit e75ca7ff8ed08e9d4fa88329c7bcf2086b99334b
Author: Georges Khaznadar <georgesk at debian.org>
Date:   Sat Jan 13 17:49:17 2018 +0100

    New upstream version 1.3.6
---
 .gitignore                |   3 ++
 .travis.yml               |  10 +++++
 HOWTODIST                 |  13 ++++--
 README.md                 |  12 +++++-
 examples/odtmerge.py      |  95 ++++++++++++++++++++++++++++++++++++++++
 examples/passwd-as-ods.py |  57 +++++++++++-------------
 odf/element.py            | 107 +++++++++++++++++++++++++++++++++++-----------
 odf/namespaces.py         |   2 +-
 odf/opendocument.py       |   4 +-
 setup                     |   2 -
 setup.cfg                 |   7 +++
 setup.py                  |   2 +-
 tests/Makefile            |  10 -----
 tests/runtests            |  12 ------
 tests/teststyles.py       |   4 +-
 tests/testunicode.py      |  14 +++++-
 tox.ini                   |  12 ++++++
 17 files changed, 276 insertions(+), 90 deletions(-)

diff --git a/.gitignore b/.gitignore
index b92d662..688bd6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
 build/
 *.pyc
+.tox/
+.cache/
+*.egg-info/
diff --git a/.travis.yml b/.travis.yml
index d1ad0ae..67cb5a4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1 +1,11 @@
 language: python
+python:
+    - 2.7
+    - 3.3
+    - 3.4
+    - 3.5
+    - 3.6
+install:
+    - pip install tox-travis
+script:
+    - tox
diff --git a/HOWTODIST b/HOWTODIST
index a5b8b20..291898c 100644
--- a/HOWTODIST
+++ b/HOWTODIST
@@ -11,7 +11,13 @@ $ cd odfpy
 
 Run the automated tests:
 
-$ cd tests ; make ; cd ..
+Install `tox` via `pip` when running the tests for the first time:
+
+$ pip install tox
+
+Run the tests for all supported python versions:
+
+$ tox
 
 Remove the "dev" marker from the version in setup.py and odf/namespaces.py
 
@@ -19,8 +25,9 @@ $ vi setup.py odf/namespaces.py
 
 ~check in the difference locally~
 
-$ git tag -a release-1.2.X -m "Tagging the 1.2.X release of the 'odfpy' project."
-$ git push origin release-1.2.X
+$ git ci -a
+$ git tag -a release-1.3.X -m "Tagging the 1.3.X release of the 'odfpy' project."
+$ git push origin release-1.3.X
 or:
 $ git push origin --tags
 
diff --git a/README.md b/README.md
index 5eed765..5c5b9dd 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,17 @@ The library is incompatible with PyXML.
 
 ## RUNNING TESTS
 
-To run the tests, `cd` into the tests directory and run `make`.
+Install `tox` via `pip` when running the tests for the first time:
+
+```
+$ pip install tox
+```
+
+Run the tests for all supported python versions:
+
+```
+$ tox
+```
 
 ## REDISTRIBUTION LICENSE
 
diff --git a/examples/odtmerge.py b/examples/odtmerge.py
new file mode 100644
index 0000000..a9fef8c
--- /dev/null
+++ b/examples/odtmerge.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2008 Søren Roug, European Environment Agency
+#
+# This is free software.  You may redistribute it under the terms
+# of the Apache license and the GNU General Public License Version
+# 2 or at your option any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# Contributor(s): Ramiro Batista da Luz
+#
+
+# Inspired by ods2odt.py
+#
+import sys, getopt
+import zipfile, xml.dom.minidom
+from odf.opendocument import OpenDocumentText, load
+from odf.element import Text
+from odf.text import P
+
+
+def usage():
+   sys.stderr.write("Usage: %s -o outputfile inputfile [inputfile2 inputfile3 ...]\n" % sys.argv[0])
+
+
+def merge(inputfile, textdoc):
+    inputtextdoc = load(inputfile)
+
+    # Need to make a copy of the list because addElement unlinks from the original
+    for meta in inputtextdoc.meta.childNodes[:]:
+        textdoc.meta.addElement(meta)
+
+    for font in inputtextdoc.fontfacedecls.childNodes[:]:
+        textdoc.fontfacedecls.addElement(font)
+
+    for style in inputtextdoc.styles.childNodes[:]:
+        textdoc.styles.addElement(style)
+
+    for autostyle in inputtextdoc.automaticstyles.childNodes[:]:
+        textdoc.automaticstyles.addElement(autostyle)
+
+
+    for scripts in inputtextdoc.scripts.childNodes[:]:
+        textdoc.scripts.addElement(scripts)
+
+    for settings in inputtextdoc.settings.childNodes[:]:
+        textdoc.settings.addElement(settings)
+
+    for masterstyles in inputtextdoc.masterstyles.childNodes[:]:
+        textdoc.masterstyles.addElement(masterstyles)
+
+    for body in inputtextdoc.body.childNodes[:]:
+        textdoc.body.addElement(body)
+
+    textdoc.Pictures = inputtextdoc.Pictures
+    return textdoc
+
+
+if __name__ == "__main__":
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "o:", ["output="])
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+
+    outputfile = None
+
+    for o, a in opts:
+        if o in ("-o", "--output"):
+            outputfile = a
+
+    if outputfile is None:
+        usage()
+        sys.exit(2)
+
+    if len(args) < 2:
+        usage()
+        sys.exit(2)
+
+    inputfiles = args[1:]
+
+    textdoc = OpenDocumentText()
+
+    for inputfile in inputfiles:
+        textdoc = merge(inputfile, textdoc)
+
+    textdoc.save(outputfile)
diff --git a/examples/passwd-as-ods.py b/examples/passwd-as-ods.py
index 4391244..8e43950 100644
--- a/examples/passwd-as-ods.py
+++ b/examples/passwd-as-ods.py
@@ -19,46 +19,41 @@
 #
 
 from odf.opendocument import OpenDocumentSpreadsheet
-from odf.style import Style, TextProperties, ParagraphProperties, TableColumnProperties
+from odf.style import (ParagraphProperties, Style, TableColumnProperties,
+                       TextProperties)
+from odf.table import Table, TableCell, TableColumn, TableRow
 from odf.text import P
-from odf.table import Table, TableColumn, TableRow, TableCell
-
-PWENC = "utf-8"
 
 textdoc = OpenDocumentSpreadsheet()
 # Create a style for the table content. One we can modify
 # later in the word processor.
-tablecontents = Style(name="Table Contents", family="paragraph")
-tablecontents.addElement(ParagraphProperties(numberlines="false", linenumber="0"))
-tablecontents.addElement(TextProperties(fontweight="bold"))
-textdoc.styles.addElement(tablecontents)
+tablecontents = Style(parent=textdoc.styles,
+                      name='Table Contents', family='paragraph')
+ParagraphProperties(parent=tablecontents, numberlines='false', linenumber='0')
+TextProperties(parent=tablecontents, fontweight='bold')
 
 # Create automatic styles for the column widths.
 # We want two different widths, one in inches, the other one in metric.
 # ODF Standard section 15.9.1
-widthshort = Style(name="Wshort", family="table-column")
-widthshort.addElement(TableColumnProperties(columnwidth="1.7cm"))
-textdoc.automaticstyles.addElement(widthshort)
+widthshort = Style(parent=textdoc.automaticstyles,
+                   name='Wshort', family='table-column')
+TableColumnProperties(parent=widthshort, columnwidth='1.7cm')
 
-widthwide = Style(name="Wwide", family="table-column")
-widthwide.addElement(TableColumnProperties(columnwidth="1.5in"))
-textdoc.automaticstyles.addElement(widthwide)
+widthwide = Style(parent=textdoc.automaticstyles,
+                  name='Wwide', family='table-column')
+TableColumnProperties(parent=widthwide, columnwidth='1.5in')
 
 # Start the table, and describe the columns
-table = Table(name="Password")
-table.addElement(TableColumn(numbercolumnsrepeated=4,stylename=widthshort))
-table.addElement(TableColumn(numbercolumnsrepeated=3,stylename=widthwide))
-
-f = open('/etc/passwd')
-for line in f:
-    rec = line.strip().split(":")
-    tr = TableRow()
-    table.addElement(tr)
-    for val in rec:
-        tc = TableCell()
-        tr.addElement(tc)
-        p = P(stylename=tablecontents,text=unicode(val,PWENC))
-        tc.addElement(p)
-
-textdoc.spreadsheet.addElement(table)
-textdoc.save("passwd.ods")
+table = Table(parent=textdoc.spreadsheet, name='Password')
+TableColumn(parent=table, numbercolumnsrepeated=4, stylename=widthshort)
+TableColumn(parent=table, numbercolumnsrepeated=3, stylename=widthwide)
+
+with open('/etc/passwd') as f:
+    for line in f:
+        rec = line.strip().split(':')
+        tr = TableRow(parent=table)
+        for val in rec:
+            tc = TableCell(parent=tr)
+            p = P(parent=tc, stylename=tablecontents, text=val)
+
+textdoc.save('passwd.ods')
diff --git a/odf/element.py b/odf/element.py
index 7ee9c1c..7dc3e2d 100644
--- a/odf/element.py
+++ b/odf/element.py
@@ -24,6 +24,7 @@
 #
 import sys, os.path
 sys.path.append(os.path.dirname(__file__))
+import re
 import xml.dom
 from xml.dom.minicompat import *
 from odf.namespaces import nsdict
@@ -32,6 +33,60 @@ from odf.attrconverters import AttrConverters
 
 if sys.version_info[0] == 3:
     unicode=str # unicode function does not exist
+    unichr=chr  # unichr does not exist
+
+_xml11_illegal_ranges = (
+    (0x0, 0x0,),
+    (0xd800, 0xdfff,),
+    (0xfffe, 0xffff,),
+)
+
+_xml10_illegal_ranges = _xml11_illegal_ranges + (
+    (0x01, 0x08,),
+    (0x0b, 0x0c,),
+    (0x0e, 0x1f,),
+)
+
+_xml_discouraged_ranges = (
+    (0x7f, 0x84,),
+    (0x86, 0x9f,),
+)
+
+if sys.maxunicode >= 0x10000:
+    # modern or "wide" python build
+    _xml_discouraged_ranges = _xml_discouraged_ranges + (
+        (0x1fffe, 0x1ffff,),
+        (0x2fffe, 0x2ffff,),
+        (0x3fffe, 0x3ffff,),
+        (0x4fffe, 0x4ffff,),
+        (0x5fffe, 0x5ffff,),
+        (0x6fffe, 0x6ffff,),
+        (0x7fffe, 0x7ffff,),
+        (0x8fffe, 0x8ffff,),
+        (0x9fffe, 0x9ffff,),
+        (0xafffe, 0xaffff,),
+        (0xbfffe, 0xbffff,),
+        (0xcfffe, 0xcffff,),
+        (0xdfffe, 0xdffff,),
+        (0xefffe, 0xeffff,),
+        (0xffffe, 0xfffff,),
+        (0x10fffe, 0x10ffff,),
+    )
+# else "narrow" python build - only possible with old versions
+
+def _range_seq_to_re(range_seq):
+    # range pairs are specified as closed intervals
+    return re.compile(u"[{}]".format(
+        u"".join(
+            u"{}-{}".format(re.escape(unichr(lo)), re.escape(unichr(hi)))
+            for lo, hi in range_seq
+        )
+    ), flags=re.UNICODE)
+
+_xml_filtered_chars_re = _range_seq_to_re(_xml10_illegal_ranges + _xml_discouraged_ranges)
+
+def _handle_unrepresentable(data):
+    return _xml_filtered_chars_re.sub(u"\ufffd", data)
 
 # The following code is pasted form xml.sax.saxutils
 # Tt makes it possible to run the code without the xml sax package installed
@@ -50,6 +105,9 @@ def _escape(data, entities={}):
         data = data.replace(chars, entity)
     return data
 
+def _sanitize(data, entities={}):
+    return _escape(_handle_unrepresentable(data), entities=entities)
+
 def _quoteattr(data, entities={}):
     """ Escape and quote an attribute value.
 
@@ -63,7 +121,7 @@ def _quoteattr(data, entities={}):
     """
     entities['\n']='
'
     entities['\r']=''
-    data = _escape(data, entities)
+    data = _sanitize(data, entities)
     if '"' in data:
         if "'" in data:
             data = '"%s"' % data.replace('"', """)
@@ -259,7 +317,7 @@ class Text(Childless, Node):
     def toXml(self,level,f):
         """ Write XML in UTF-8 """
         if self.data:
-            f.write(_escape(unicode(self.data)))
+            f.write(_sanitize(unicode(self.data)))
     
 class CDATASection(Text, Childless):
     nodeType = Node.CDATA_SECTION_NODE
@@ -289,7 +347,7 @@ class Element(Node):
                          Node.TEXT_NODE,
                          Node.CDATA_SECTION_NODE,
                          Node.ENTITY_REFERENCE_NODE)
-    
+
     def __init__(self, attributes=None, text=None, cdata=None, qname=None, qattributes=None, check_grammar=True, **args):
         if qname is not None:
             self.qname = qname
@@ -340,7 +398,7 @@ class Element(Node):
         for ns,p in nsdict.items():
             if p == prefix: return ns
         return None
-        
+
     def get_nsprefix(self, namespace):
         """ Odfpy maintains a list of known namespaces. In some cases we have a namespace URL,
             and needs to look up or assign the prefix for it.
@@ -358,7 +416,7 @@ class Element(Node):
         element.ownerDocument = self.ownerDocument
         for child in element.childNodes:
             self._setOwnerDoc(child)
-        
+
     def addElement(self, element, check_grammar=True):
         """ adds an element to an Element
 
@@ -416,20 +474,23 @@ class Element(Node):
             library will add the correct namespace.
             Must overwrite, If attribute already exists.
         """
-        allowed_attrs = self.allowed_attributes()
-        if allowed_attrs is None:
-            if type(attr) == type(()):
-                prefix, localname = attr
-                self.setAttrNS(prefix, localname, value)
-            else:
-                raise AttributeError( "Unable to add simple attribute - use (namespace, localpart)")
+        if attr == 'parent' and value is not None:
+            value.addElement(self)
         else:
-            # Construct a list of allowed arguments
-            allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
-            if check_grammar and attr not in allowed_args:
-                raise AttributeError( "Attribute %s is not allowed in <%s>" % ( attr, self.tagName))
-            i = allowed_args.index(attr)
-            self.setAttrNS(allowed_attrs[i][0], allowed_attrs[i][1], value)
+            allowed_attrs = self.allowed_attributes()
+            if allowed_attrs is None:
+                if type(attr) == type(()):
+                    prefix, localname = attr
+                    self.setAttrNS(prefix, localname, value)
+                else:
+                    raise AttributeError( "Unable to add simple attribute - use (namespace, localpart)")
+            else:
+                # Construct a list of allowed arguments
+                allowed_args = [ a[1].lower().replace('-','') for a in allowed_attrs]
+                if check_grammar and attr not in allowed_args:
+                    raise AttributeError( "Attribute %s is not allowed in <%s>" % ( attr, self.tagName))
+                i = allowed_args.index(attr)
+                self.setAttrNS(allowed_attrs[i][0], allowed_attrs[i][1], value)
 
     def setAttrNS(self, namespace, localpart, value):
         """ Add an attribute to the element
@@ -490,10 +551,10 @@ class Element(Node):
         f.write(('<'+self.tagName))
         if level == 0:
             for namespace, prefix in self.namespaces.items():
-                f.write(u' xmlns:' + prefix + u'="'+ _escape(str(namespace))+'"')
+                f.write(u' xmlns:' + prefix + u'="'+ _sanitize(str(namespace))+'"')
         for qname in self.attributes.keys():
             prefix = self.get_nsprefix(qname[0])
-            f.write(u' '+_escape(str(prefix+u':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
+            f.write(u' '+_sanitize(str(prefix+u':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
         f.write(u'>')
 
     def write_close_tag(self, level, f):
@@ -508,10 +569,10 @@ class Element(Node):
         f.write(u'<'+self.tagName)
         if level == 0:
             for namespace, prefix in self.namespaces.items():
-                f.write(u' xmlns:' + prefix + u'="'+ _escape(str(namespace))+u'"')
+                f.write(u' xmlns:' + prefix + u'="'+ _sanitize(str(namespace))+u'"')
         for qname in self.attributes.keys():
             prefix = self.get_nsprefix(qname[0])
-            f.write(u' '+_escape(unicode(prefix+':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
+            f.write(u' '+_sanitize(unicode(prefix+':'+qname[1]))+u'='+_quoteattr(unicode(self.attributes[qname])))
         if self.childNodes:
             f.write(u'>')
             for element in self.childNodes:
@@ -537,5 +598,3 @@ class Element(Node):
         """ This is a check to see if the object is an instance of a type """
         obj = element(check_grammar=False)
         return self.qname == obj.qname
-
-
diff --git a/odf/namespaces.py b/odf/namespaces.py
index 525a9b4..02fac0a 100644
--- a/odf/namespaces.py
+++ b/odf/namespaces.py
@@ -17,7 +17,7 @@
 #
 # Contributor(s):
 #
-__version__ = "1.3.5"
+__version__ = "1.3.6"
 
 TOOLSVERSION = u"ODFPY/" + __version__
 
diff --git a/odf/opendocument.py b/odf/opendocument.py
index 9b8191e..1b3f8aa 100644
--- a/odf/opendocument.py
+++ b/odf/opendocument.py
@@ -532,7 +532,7 @@ class OpenDocument:
             self.manifest.addElement(manifest.FileEntry(fullpath=u"%s%s" % ( folder ,arcname), mediatype=mediatype))
             hasPictures = True
             if what_it_is == IS_FILENAME:
-                self._z.write(fileobj, arcname, zipfile.ZIP_STORED)
+                self._z.write(fileobj, folder + arcname, zipfile.ZIP_STORED)
             else:
                 zi = zipfile.ZipInfo(str(arcname), self._now)
                 zi.compress_type = zipfile.ZIP_STORED
@@ -864,7 +864,7 @@ def __loadxmlparts(z, manifest, doc, objectpath):
     assert(isinstance(doc, OpenDocument))
     assert(type(objectpath)==type(u""))
 
-    from load import LoadParser
+    from odf.load import LoadParser
     from xml.sax import make_parser, handler
 
     for xmlfile in (objectpath+u'settings.xml', objectpath+u'meta.xml', objectpath+u'content.xml', objectpath+u'styles.xml'):
diff --git a/setup b/setup
deleted file mode 100644
index 3c6e79c..0000000
--- a/setup
+++ /dev/null
@@ -1,2 +0,0 @@
-[bdist_wheel]
-universal=1
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..1935cf0
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,7 @@
+[bdist_wheel]
+universal=1
+
+[tool:pytest]
+norecursedirs = grammar
+addopts = tests
+python_files = test*.py
diff --git a/setup.py b/setup.py
index 90f0861..e920fb3 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@
 import platform
 from setuptools import setup
 
-version = '1.3.5'
+version = '1.3.6'
 
 if platform.system() in ('Linux','Unix'):
     man1pages = [('share/man/man1', [
diff --git a/tests/Makefile b/tests/Makefile
deleted file mode 100644
index 7d57de3..0000000
--- a/tests/Makefile
+++ /dev/null
@@ -1,10 +0,0 @@
-all: odf runall
-
-runall:
-	./runtests
-
-odf:
-	ln -s ../odf
-
-clean:
-	rm -f *.1 *.txt odf
diff --git a/tests/runtests b/tests/runtests
deleted file mode 100755
index ffa3a8f..0000000
--- a/tests/runtests
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/sh
-echo '========= Python 2 tests =========='
-for file in test*.py
-do
-    python2  $file
-done
-
-echo '========= Python 3 tests =========='
-for file in test*.py
-do
-    python3 $file
-done
diff --git a/tests/teststyles.py b/tests/teststyles.py
index e9c318b..cca8b93 100644
--- a/tests/teststyles.py
+++ b/tests/teststyles.py
@@ -88,8 +88,8 @@ class TestQattributes(unittest.TestCase):
         textdoc.styles.addElement(standard)
         s = textdoc.stylesxml()
         s.index(u"""<?xml version='1.0' encoding='UTF-8'?>\n""")
-        s.index(u'xmlns:ns41="http://foreignuri.com"')
-        s.index(u'<style:paragraph-properties ns41:enable-numbering="true"/>')
+        s.index(u'xmlns:ns44="http://foreignuri.com"')
+        s.index(u'<style:paragraph-properties ns44:enable-numbering="true"/>')
         e = ElementParser(s,u'style:style')
 #        e = ElementParser('<style:style style:name="Standard" style:display-name="Standard" style:family="paragraph">')
         self.assertEqual(e.element,u'style:style')
diff --git a/tests/testunicode.py b/tests/testunicode.py
index b3e2cc7..1ac0e60 100644
--- a/tests/testunicode.py
+++ b/tests/testunicode.py
@@ -40,6 +40,8 @@ class TestUnicode(unittest.TestCase):
     def assertNotContains(self, stack, needle):
         self.assertEqual(-1, stack.find(needle))
 
+    @unittest.skipIf(sys.version_info[0] != 2,
+                     "For Python3, unicode strings are type 'str'.")
     def test_xstyle(self):
         self.assertRaises(UnicodeDecodeError, style.Style, name="X✗", family="paragraph")
         xstyle = style.Style(name=u"X✗", family=u"paragraph")
@@ -61,11 +63,21 @@ class TestUnicode(unittest.TestCase):
         p = H(outlinelevel=1,text=u"Æblegrød")
         p.addText(u' Blåbærgrød')
         self.textdoc.text.addElement(p)
-        c = self.textdoc.contentxml() # contentxml is supposed to yeld a bytes
+        c = self.textdoc.contentxml() # contentxml is supposed to yield a bytes
         self.assertContains(c, b'<office:body><office:text><text:h text:outline-level="1">\xc3\x86blegr\xc3\xb8d Bl\xc3\xa5b\xc3\xa6rgr\xc3\xb8d</text:h></office:text></office:body>')
         self.assertContains(c, b'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"')
         self.assertContains(c, b'<office:automatic-styles/>')
 
+    def test_illegaltext(self):
+        p = H(outlinelevel=1,text=u"Spot \u001e the")
+        p.addText(u' d\u00a3libe\u0000rate \ud801 mistakes\U0002fffe')
+        self.textdoc.text.addElement(p)
+        c = self.textdoc.contentxml() # contentxml is supposed to yield a bytes
+        # unicode replacement char \UFFFD === \xef\xbf\xbd in UTF-8
+        self.assertContains(c, b'<office:body><office:text><text:h text:outline-level="1">Spot \xef\xbf\xbd the d\xc2\xa3libe\xef\xbf\xbdrate \xef\xbf\xbd mistakes\xef\xbf\xbd</text:h></office:text></office:body>')
+        self.assertContains(c, b'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"')
+        self.assertContains(c, b'<office:automatic-styles/>')
+
 if __name__ == '__main__':
     if sys.version_info[0]==2:
         unittest.main()
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..b5b42d4
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,12 @@
+[tox]
+envlist =
+    py27,
+    py34,
+    py35,
+    py36,
+
+[testenv]
+commands =
+    pytest
+deps =
+    pytest

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-odf.git



More information about the Python-modules-commits mailing list