diff --git a/.travis.yml b/.travis.yml index 2f45864..c055dce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,8 @@ before_install: # command to install dependencies install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors lxml contextlib2 mock; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors lxml numpy; fi # command to run tests script: diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index ad4e40b..bbba98e 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -5,6 +5,7 @@ ChangeLog 0.6.0 (pending) =============== + * Added lxml requirement. * added set_printoptions, get_printoptions function * dropped support for Python 2.6, added support for Python 3.4 * dropped windows support diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index d1b3084..cfbb168 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -68,8 +68,8 @@ For python 3.3, you should install the following set of ports: * python33 * py33-numpy + * py33-lxml * py33-distribute - * py33-matplotlib (optional, for running certain tests) * py33-Pillow (optional, for running certain tests) MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0. @@ -85,6 +85,7 @@ packages may also need to be installed. Consult your package manager documentation or use pip. * setuptools + * python-lxml * matplotlib * pillow * contextlib2 (2.7 only) diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index fa8f098..4bde854 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -131,7 +131,6 @@ From within Python, it is as simple as printing the Jp2k object, i.e. :: - Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) @@ -228,7 +227,6 @@ The :py:meth:`append` method can add an XML box as shown below:: >>> import shutil >>> import glymur >>> shutil.copyfile(glymur.data.nemo(), 'myfile.jp2') - >>> from xml.etree import cElementTree as ET >>> jp2 = glymur.Jp2k('myfile.jp2') >>> xmlbox = glymur.jp2box.XMLBox(filename='data.xml') >>> jp2.append(xmlbox) @@ -408,12 +406,12 @@ The example JP2 file shipped with glymur has an XMP UUID. :: . -Since the UUID data in this case is returned as an ElementTree instance, -one can use ElementTree from the standard library to access the data. -For example, to extract the **CreatorTool** attribute value, one could do the +Since the UUID data in this case is returned as an lxml ElementTree +instance, one can use lxml to access the data. For example, to +extract the **CreatorTool** attribute value, one could do the following - >>> xmp = j.box[3].data.packet + >>> xmp = j.box[3].data >>> rdf = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' >>> ns2 = '{http://ns.adobe.com/xap/1.0/}' >>> name = '{0}RDF/{0}Description/{1}CreatorTool'.format(rdf, ns2) diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py index ed610e5..399fa99 100644 --- a/glymur/_uuid_io.py +++ b/glymur/_uuid_io.py @@ -7,7 +7,8 @@ import re import struct import sys import warnings -import xml.etree.cElementTree as ET + +import lxml.etree as ET if sys.hexversion < 0x02070000: # pylint: disable=F0401,E0611 diff --git a/glymur/core.py b/glymur/core.py index 620d0e3..4e8f950 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -1,7 +1,7 @@ """Core definitions to be shared amongst the modules. """ import copy -import xml.etree.cElementTree as ET +import lxml.etree as ET # Progression order LRCP = 0 @@ -76,45 +76,3 @@ _CAPABILITIES_DISPLAY = { 1: '0', 2: '1', 3: '3'} - - -def _pretty_print_xml(xml, level=0): - """Pretty print XML data. - """ - xml = copy.deepcopy(xml) - _indent(xml.getroot(), level=level) - xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8') - - # Indent it a bit. - lst = [(' ' + x) for x in xmltext.split('\n')] - try: - xml = '\n'.join(lst) - return '\n{0}'.format(xml) - except UnicodeEncodeError: - # This can happen on python 2.x if the character set contains certain - # non-ascii characters. Just print out the corresponding xml char - # entities instead. - xml = u'\n'.join(lst) - text = u'\n{0}'.format(xml) - text = text.encode('ascii', 'xmlcharrefreplace') - return text - - -def _indent(elem, level=0): - """Recipe for pretty printing XML. Please see - - http://effbot.org/zone/element-lib.htm#prettyprint - """ - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - _indent(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 3cf637d..5a0febf 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -20,10 +20,11 @@ import os import pprint import struct import sys +import textwrap import uuid import warnings -import xml.etree.cElementTree as ET +import lxml.etree as ET import numpy as np from .codestream import Codestream @@ -32,7 +33,6 @@ from .core import _COLOR_TYPE_MAP_DISPLAY from .core import SRGB, GREYSCALE, YCC from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD -from .core import _pretty_print_xml from . import _uuid_io @@ -97,10 +97,8 @@ class Jp2kBox(object): msg = Jp2kBox.__str__(self) for box in self.box: boxstr = str(box) - - # Add indentation. - strs = [('\n ' + x) for x in boxstr.split('\n')] - msg += ''.join(strs) + # Indent the child boxes to make the association clear. + msg += '\n' + textwrap.indent(boxstr, ' ') return msg @@ -890,10 +888,7 @@ class ContiguousCodestreamBox(Jp2kBox): msg += '\n Main header:' for segment in self.main_header.segment: - segstr = str(segment) - # Add indentation. - strs = [('\n ' + x) for x in segstr.split('\n')] - msg += ''.join(strs) + msg += '\n' + textwrap.indent(str(segment), ' ') return msg @@ -2563,11 +2558,14 @@ class XMLBox(Jp2kBox): if _printoptions['xml'] == False: return msg - xml = self.xml + msg += '\n' if self.xml is not None: - msg += _pretty_print_xml(self.xml) + xmlstring = ET.tostring(self.xml, + encoding='utf-8', + pretty_print=True).decode('utf-8') else: - msg += '\n {0}'.format(xml) + xmlstring = 'None' + msg += textwrap.indent(xmlstring, ' ') return msg def write(self, fptr): @@ -2971,9 +2969,12 @@ class UUIDBox(Jp2kBox): return msg if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - line = '\n UUID Data: {0}' - xmlstring = _pretty_print_xml(self.data) - xmlstring = xmlstring.rstrip() + line = '\n UUID Data:\n{0}' + xmlstring = ET.tostring(self.data, + encoding='utf-8', + pretty_print=True).decode('utf-8') + # indent it a bit + xmlstring = textwrap.indent(xmlstring.rstrip(), ' ') msg += line.format(xmlstring) elif self.uuid.bytes == b'JpgTiffExif->JP2': msg += '\n UUID Data: {0}'.format(str(self.data)) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index a7906c5..fe0777e 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -4,6 +4,7 @@ Test fixtures common to more than one test point. import os import re import sys +import textwrap import warnings import numpy as np @@ -181,92 +182,100 @@ def read_pgx_header(pgx_file): header = header.rstrip() return header, pos +nemo_xmp = """ + + + + Google + 2013-02-09T14:47:53 + + + 1 + 72/1 + 72/1 + 2 + HTC + HTC Glacier + 2592 + 1456 + + + 8 + 8 + 8 + + + 2 + 3 + + + 1343036288/4294967295 + 1413044224/4294967295 + + + + + 2748779008/4294967295 + 1417339264/4294967295 + 1288490240/4294967295 + 2576980480/4294967295 + 644245120/4294967295 + 257698032/4294967295 + + + + + 1 + 2528 + 1424 + 353/100 + 0 + 0/1 + WGS-84 + 2013-02-09T14:47:53 + + + 76 + + + 0220 + 0100 + + + 1 + 2 + 3 + 0 + + + 42,20.56N + 71,5.29W + 2013-02-09T19:47:53Z + NETWORK + + + 2013-02-09T14:47:53 + + + + + Glymur + Python XMP Toolkit + + + + + +""" nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - """ + UUID Data: +{0}""".format(textwrap.indent(nemo_xmp, ' ')) + +nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: +{0}""".format(textwrap.indent(nemo_xmp, ' ')) SimpleRDF = """ - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - + UUID Data: +{0} Contiguous Codestream Box (jp2c) @ (3223, 1132296) Main header: SOC marker segment @ (3231, 0) @@ -492,6 +419,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] CME marker segment @ (3305, 37) "Created by OpenJPEG version 2.0.0"''' +nemo_dump_full = dump.format(textwrap.indent(nemo_xmp, ' ')) nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) File Type Box (ftyp) @ (12, 20) @@ -557,7 +485,7 @@ Contiguous Codestream Box (jp2c) @ (3223, 1132296) CME marker segment @ (3305, 37) "Created by OpenJPEG version 2.0.0"''' -nemo_dump_no_codestream = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) +dump = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) Brand: jp2 @@ -575,91 +503,10 @@ JP2 Header Box (jp2h) @ (32, 45) Colorspace: sRGB UUID Box (uuid) @ (77, 3146) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - + UUID Data: +{0} Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" +nemo_dump_no_codestream = dump.format(textwrap.indent(nemo_xmp, ' ')) nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a @@ -691,10 +538,10 @@ file7_rreq = r"""Reader Requirements Box (rreq) @ (44, 24) Feature 043: 0x40 Deprecated - compositing layer uses restricted ICC profile Vendor Features:""" -file1_xml = r"""XML Box (xml ) @ (36, 439) - - - 2001-11-01T13:45:00.000-06:00 - Professional 120 Image - - """ +file1_xml = """XML Box (xml ) @ (36, 439) + + \t + \t\t2001-11-01T13:45:00.000-06:00 + \t\tProfessional 120 Image + \t + """ diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 5861971..5dedbcc 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -22,10 +22,10 @@ import sys import tempfile import uuid from uuid import UUID -import xml.etree.cElementTree as ET import unittest import warnings +import lxml.etree as ET import numpy as np import glymur @@ -1056,7 +1056,7 @@ class TestRepr(unittest.TestCase): box = glymur.jp2box.XMLBox(xml=tree) regexp = r"""glymur.jp2box.XMLBox""" - regexp += r"""\(xml=<(xml.etree.ElementTree.){0,1}ElementTree object """ + regexp += r"""\(xml=\)""" if sys.hexversion < 0x03000000: diff --git a/glymur/test/test_jp2box_jpx.py b/glymur/test/test_jp2box_jpx.py index 7493dea..f8e9db8 100644 --- a/glymur/test/test_jp2box_jpx.py +++ b/glymur/test/test_jp2box_jpx.py @@ -8,7 +8,7 @@ import struct import sys import tempfile import unittest -import xml.etree.cElementTree as ET +import lxml.etree as ET import glymur from glymur import Jp2k diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py index 82dc253..a904174 100644 --- a/glymur/test/test_jp2box_uuid.py +++ b/glymur/test/test_jp2box_uuid.py @@ -18,7 +18,6 @@ import sys import tempfile import uuid import warnings -import xml.etree if sys.hexversion < 0x02070000: import unittest2 as unittest @@ -35,6 +34,8 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch +import lxml.etree + from .fixtures import HAS_PYTHON_XMP_TOOLKIT, OPJ_DATA_ROOT if HAS_PYTHON_XMP_TOOLKIT: from libxmp import XMPMeta @@ -71,7 +72,7 @@ class TestUUIDXMP(unittest.TestCase): # The data should be an XMP packet, which gets interpreted as # an ElementTree. self.assertTrue(isinstance(jp2.box[-1].data, - xml.etree.ElementTree.ElementTree)) + lxml.etree._ElementTree)) class TestUUIDExif(unittest.TestCase): """Tests for UUIDs of Exif type.""" diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index 4950da4..facc4df 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -20,7 +20,6 @@ import sys import tempfile import unittest import warnings -import xml.etree.cElementTree as ET if sys.hexversion < 0x03000000: from StringIO import StringIO @@ -32,6 +31,8 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch +import lxml.etree as ET + import glymur from glymur import Jp2k from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 132d1ec..882f57c 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -15,7 +15,6 @@ import struct import sys import tempfile import warnings -from xml.etree import cElementTree as ET import unittest if sys.hexversion < 0x03000000: @@ -28,10 +27,12 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch +import lxml.etree as ET + import glymur from glymur import Jp2k from . import fixtures -from .fixtures import OPJ_DATA_ROOT, opj_data_file, nemo_xmp_box +from .fixtures import OPJ_DATA_ROOT, opj_data_file from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 @@ -567,7 +568,7 @@ class TestPrinting(unittest.TestCase): print(j.box[3]) actual = fake_out.getvalue().strip() - expected = nemo_xmp_box + expected = fixtures.nemo_xmp_box self.assertEqual(actual, expected) def test_codestream(self): @@ -639,8 +640,7 @@ class TestPrinting(unittest.TestCase): # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. - text = u""" - Strömung""" + text = u"""Strömung""" if sys.hexversion < 0x03000000: xml = ET.parse(StringIO(text.encode('utf-8'))) else: @@ -668,8 +668,7 @@ class TestPrinting(unittest.TestCase): # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. - text = u""" - Россия""" + text = u"""Россия""" if sys.hexversion < 0x03000000: xml = ET.parse(StringIO(text.encode('utf-8'))) else: diff --git a/setup.py b/setup.py index b1332d8..5b91590 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ kwargs = {'name': 'Glymur', 'license': 'MIT', 'test_suite': 'glymur.test'} -instllrqrs = ['numpy>=1.4.1'] +instllrqrs = ['numpy>=1.4.1', 'lxml>=2.3.2'] if sys.hexversion < 0x03030000: instllrqrs.append('contextlib2>=0.4') instllrqrs.append('mock>=1.0.1')