diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 71dbd72..72b9f9d 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -320,113 +320,6 @@ class TestChannelDefinition(unittest.TestCase): association=association) -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -class TestXML(unittest.TestCase): - """Test suite for XML boxes.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - raw_xml = b""" - - - 1 - 2008 - 141100 - - - - - 4 - 2011 - 59900 - - - - 68 - 2011 - 13600 - - - - """ - with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile: - tfile.write(raw_xml) - tfile.flush() - self.xmlfile = tfile.name - - j2k = Jp2k(self.j2kfile) - codestream = j2k.get_codestream() - height = codestream.segment[1].ysiz - width = codestream.segment[1].xsiz - num_components = len(codestream.segment[1].xrsiz) - - self.jp2b = JPEG2000SignatureBox() - self.ftyp = FileTypeBox() - self.jp2h = JP2HeaderBox() - self.jp2c = ContiguousCodestreamBox() - self.ihdr = ImageHeaderBox(height=height, width=width, - num_components=num_components) - self.colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) - - def tearDown(self): - os.unlink(self.xmlfile) - - def test_negative_file_and_xml(self): - """The XML should come from only one source.""" - xml_object = ET.parse(self.xmlfile) - with self.assertRaises((IOError, OSError)): - glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object) - - @unittest.skipIf(os.name == "nt", - "Problems using NamedTemporaryFile on windows.") - def test_basic_xml(self): - """Should be able to write a basic XMLBox""" - j2k = Jp2k(self.j2kfile) - - self.jp2h.box = [self.ihdr, self.colr] - - the_xml = ET.fromstring('0') - xmlb = glymur.jp2box.XMLBox(xml=the_xml) - self.assertEqual(ET.tostring(xmlb.xml), - b'0') - - boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c] - - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - j2k.wrap(tfile.name, boxes=boxes) - jp2 = Jp2k(tfile.name) - self.assertEqual(jp2.box[3].box_id, 'xml ') - self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()), - b'0') - - @unittest.skipIf(os.name == "nt", - "Problems using NamedTemporaryFile on windows.") - def test_xml_from_file(self): - """Must be able to create an XML box from an XML file.""" - j2k = Jp2k(self.j2kfile) - - self.jp2h.box = [self.ihdr, self.colr] - - xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile) - boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c] - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - j2k.wrap(tfile.name, boxes=boxes) - jp2 = Jp2k(tfile.name) - - output_boxes = [box.box_id for box in jp2.box] - self.assertEqual(output_boxes, ['jP ', 'ftyp', 'jp2h', 'xml ', - 'jp2c']) - - elts = jp2.box[3].xml.findall('country') - self.assertEqual(len(elts), 3) - - neighbor = elts[1].find('neighbor') - self.assertEqual(neighbor.attrib['name'], 'Malaysia') - self.assertEqual(neighbor.attrib['direction'], 'N') - - class TestColourSpecificationBox(unittest.TestCase): """Test suite for colr box instantiation.""" @@ -856,5 +749,264 @@ class TestJpxBoxes(unittest.TestCase): self.assertEqual(len(jpx.box[5].box), 0) +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +class TestChannelDefinition(unittest.TestCase): + """Test suite for channel definition boxes.""" + + @classmethod + def setUpClass(cls): + """Need a one_plane plane image for greyscale testing.""" + j2k = Jp2k(glymur.data.goodstuff()) + data = j2k.read() + # Write the first component back out to file. + with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: + grey_j2k = Jp2k(tfile.name, 'wb') + grey_j2k.write(data[:, :, 0]) + cls.one_plane = tfile.name + # Write the first two components back out to file. + with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: + grey_j2k = Jp2k(tfile.name, 'wb') + grey_j2k.write(data[:, :, 0:1]) + cls.two_planes = tfile.name + # Write four components back out to file. + with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: + rgba_jp2 = Jp2k(tfile.name, 'wb') + shape = (data.shape[0], data.shape[1], 1) + alpha = np.zeros((shape), dtype=data.dtype) + data4 = np.concatenate((data, alpha), axis=2) + rgba_jp2.write(data4) + cls.four_planes = tfile.name + + @classmethod + def tearDownClass(cls): + os.unlink(cls.one_plane) + os.unlink(cls.two_planes) + os.unlink(cls.four_planes) + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + j2k = Jp2k(self.j2kfile) + codestream = j2k.get_codestream() + height = codestream.segment[1].ysiz + width = codestream.segment[1].xsiz + num_components = len(codestream.segment[1].xrsiz) + + self.jp2b = JPEG2000SignatureBox() + self.ftyp = FileTypeBox() + self.jp2h = JP2HeaderBox() + self.jp2c = ContiguousCodestreamBox() + self.ihdr = ImageHeaderBox(height=height, width=width, + num_components=num_components) + self.colr_rgb = ColourSpecificationBox(colorspace=glymur.core.SRGB) + self.colr_gr = ColourSpecificationBox(colorspace=glymur.core.GREYSCALE) + + def tearDown(self): + pass + + def test_cdef_no_inputs(self): + """channel_type and association are required inputs.""" + with self.assertRaises(IOError): + glymur.jp2box.ChannelDefinitionBox() + + def test_rgb_with_index(self): + """Just regular RGB.""" + j2k = Jp2k(self.j2kfile) + channel_type = [COLOR, COLOR, COLOR] + association = [RED, GREEN, BLUE] + cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2], + channel_type=channel_type, + association=association) + boxes = [self.ihdr, self.colr_rgb, cdef] + self.jp2h.box = boxes + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + + jp2 = Jp2k(tfile.name) + jp2h = jp2.box[2] + boxes = [box.box_id for box in jp2h.box] + self.assertEqual(boxes, ['ihdr', 'colr', 'cdef']) + self.assertEqual(jp2h.box[2].index, (0, 1, 2)) + self.assertEqual(jp2h.box[2].channel_type, + (COLOR, COLOR, COLOR)) + self.assertEqual(jp2h.box[2].association, + (RED, GREEN, BLUE)) + + def test_rgb(self): + """Just regular RGB, but don't supply the optional index.""" + j2k = Jp2k(self.j2kfile) + channel_type = [COLOR, COLOR, COLOR] + association = [RED, GREEN, BLUE] + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + boxes = [self.ihdr, self.colr_rgb, cdef] + self.jp2h.box = boxes + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + + jp2 = Jp2k(tfile.name) + jp2h = jp2.box[2] + boxes = [box.box_id for box in jp2h.box] + self.assertEqual(boxes, ['ihdr', 'colr', 'cdef']) + self.assertEqual(jp2h.box[2].index, (0, 1, 2)) + self.assertEqual(jp2h.box[2].channel_type, + (COLOR, COLOR, COLOR)) + self.assertEqual(jp2h.box[2].association, + (RED, GREEN, BLUE)) + + def test_rgba(self): + """Just regular RGBA.""" + j2k = Jp2k(self.four_planes) + channel_type = (COLOR, COLOR, COLOR, OPACITY) + association = (RED, GREEN, BLUE, WHOLE_IMAGE) + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + boxes = [self.ihdr, self.colr_rgb, cdef] + self.jp2h.box = boxes + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + + jp2 = Jp2k(tfile.name) + jp2h = jp2.box[2] + boxes = [box.box_id for box in jp2h.box] + self.assertEqual(boxes, ['ihdr', 'colr', 'cdef']) + self.assertEqual(jp2h.box[2].index, (0, 1, 2, 3)) + self.assertEqual(jp2h.box[2].channel_type, channel_type) + self.assertEqual(jp2h.box[2].association, association) + + def test_bad_rgba(self): + """R, G, and B must be specified.""" + j2k = Jp2k(self.four_planes) + channel_type = (COLOR, COLOR, OPACITY, OPACITY) + association = (RED, GREEN, BLUE, WHOLE_IMAGE) + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + boxes = [self.ihdr, self.colr_rgb, cdef] + self.jp2h.box = boxes + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with self.assertRaises(IOError): + j2k.wrap(tfile.name, boxes=boxes) + + def test_grey(self): + """Just regular greyscale.""" + j2k = Jp2k(self.one_plane) + channel_type = (COLOR,) + association = (GREY,) + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + boxes = [self.ihdr, self.colr_gr, cdef] + self.jp2h.box = boxes + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + + jp2 = Jp2k(tfile.name) + jp2h = jp2.box[2] + boxes = [box.box_id for box in jp2h.box] + self.assertEqual(boxes, ['ihdr', 'colr', 'cdef']) + self.assertEqual(jp2h.box[2].index, (0,)) + self.assertEqual(jp2h.box[2].channel_type, channel_type) + self.assertEqual(jp2h.box[2].association, association) + + def test_grey_alpha(self): + """Just regular greyscale plus alpha.""" + j2k = Jp2k(self.two_planes) + channel_type = (COLOR, OPACITY) + association = (GREY, WHOLE_IMAGE) + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + boxes = [self.ihdr, self.colr_gr, cdef] + self.jp2h.box = boxes + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + + jp2 = Jp2k(tfile.name) + jp2h = jp2.box[2] + boxes = [box.box_id for box in jp2h.box] + self.assertEqual(boxes, ['ihdr', 'colr', 'cdef']) + self.assertEqual(jp2h.box[2].index, (0, 1)) + self.assertEqual(jp2h.box[2].channel_type, channel_type) + self.assertEqual(jp2h.box[2].association, association) + + def test_bad_grey_alpha(self): + """A greyscale image with alpha layer must specify a color channel""" + j2k = Jp2k(self.two_planes) + + channel_type = (OPACITY, OPACITY) + association = (GREY, WHOLE_IMAGE) + + # This cdef box + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + boxes = [self.ihdr, self.colr_gr, cdef] + self.jp2h.box = boxes + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with self.assertRaises((OSError, IOError)): + j2k.wrap(tfile.name, boxes=boxes) + + def test_only_one_cdef_in_jp2h(self): + """There can only be one channel definition box in the jp2 header.""" + j2k = Jp2k(self.j2kfile) + + channel_type = (COLOR, COLOR, COLOR) + association = (RED, GREEN, BLUE) + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + + boxes = [self.ihdr, cdef, self.colr_rgb, cdef] + self.jp2h.box = boxes + + boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] + + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with self.assertRaises(IOError): + j2k.wrap(tfile.name, boxes=boxes) + + def test_not_in_jp2h(self): + """need cdef in jp2h""" + j2k = Jp2k(self.j2kfile) + boxes = [self.ihdr, self.colr_rgb] + self.jp2h.box = boxes + + channel_type = (COLOR, COLOR, COLOR) + association = (RED, GREEN, BLUE) + cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + + boxes = [self.jp2b, self.ftyp, self.jp2h, cdef, self.jp2c] + + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + with self.assertRaises(IOError): + j2k.wrap(tfile.name, boxes=boxes) + + def test_bad_type(self): + """Channel types are limited to 0, 1, 2, 65535 + Should reject if not all of index, channel_type, association the + same length. + """ + channel_type = (COLOR, COLOR, 3) + association = (RED, GREEN, BLUE) + with self.assertRaises(IOError): + glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + + def test_wrong_lengths(self): + """Should reject if not all of index, channel_type, association the + same length. + """ + channel_type = (COLOR, COLOR) + association = (RED, GREEN, BLUE) + with self.assertRaises(IOError): + glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, + association=association) + + if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py new file mode 100644 index 0000000..ba1ff15 --- /dev/null +++ b/glymur/test/test_jp2box_xml.py @@ -0,0 +1,266 @@ +""" +Test suite specifically targeting JP2 box layout. +""" +# E1103: return value from read may be list or np array +# pylint: disable=E1103 + +# F0401: unittest2 is needed on python-2.6 (pylint on 2.7) +# pylint: disable=F0401 + +# R0902: More than 7 instance attributes are just fine for testing. +# pylint: disable=R0902 + +# R0904: Seems like pylint is fooled in this situation +# pylint: disable=R0904 + +# W0613: load_tests doesn't need to use ignore or loader arguments. +# pylint: disable=W0613 + +import os +import struct +import sys +import tempfile +import warnings +import xml.etree.cElementTree as ET + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +import glymur +from glymur import Jp2k +from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox +from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox +from glymur.jp2box import JPEG2000SignatureBox + + +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +class TestXML(unittest.TestCase): + """Test suite for XML boxes.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + raw_xml = b""" + + + 1 + 2008 + 141100 + + + + + 4 + 2011 + 59900 + + + + 68 + 2011 + 13600 + + + + """ + with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile: + tfile.write(raw_xml) + tfile.flush() + self.xmlfile = tfile.name + + j2k = Jp2k(self.j2kfile) + codestream = j2k.get_codestream() + height = codestream.segment[1].ysiz + width = codestream.segment[1].xsiz + num_components = len(codestream.segment[1].xrsiz) + + self.jp2b = JPEG2000SignatureBox() + self.ftyp = FileTypeBox() + self.jp2h = JP2HeaderBox() + self.jp2c = ContiguousCodestreamBox() + self.ihdr = ImageHeaderBox(height=height, width=width, + num_components=num_components) + self.colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) + + def tearDown(self): + os.unlink(self.xmlfile) + + def test_negative_file_and_xml(self): + """The XML should come from only one source.""" + xml_object = ET.parse(self.xmlfile) + with self.assertRaises((IOError, OSError)): + glymur.jp2box.XMLBox(filename=self.xmlfile, xml=xml_object) + + @unittest.skipIf(os.name == "nt", + "Problems using NamedTemporaryFile on windows.") + def test_basic_xml(self): + """Should be able to write a basic XMLBox""" + j2k = Jp2k(self.j2kfile) + + self.jp2h.box = [self.ihdr, self.colr] + + the_xml = ET.fromstring('0') + xmlb = glymur.jp2box.XMLBox(xml=the_xml) + self.assertEqual(ET.tostring(xmlb.xml), + b'0') + + boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c] + + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + jp2 = Jp2k(tfile.name) + self.assertEqual(jp2.box[3].box_id, 'xml ') + self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()), + b'0') + + @unittest.skipIf(os.name == "nt", + "Problems using NamedTemporaryFile on windows.") + def test_xml_from_file(self): + """Must be able to create an XML box from an XML file.""" + j2k = Jp2k(self.j2kfile) + + self.jp2h.box = [self.ihdr, self.colr] + + xmlb = glymur.jp2box.XMLBox(filename=self.xmlfile) + boxes = [self.jp2b, self.ftyp, self.jp2h, xmlb, self.jp2c] + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j2k.wrap(tfile.name, boxes=boxes) + jp2 = Jp2k(tfile.name) + + output_boxes = [box.box_id for box in jp2.box] + self.assertEqual(output_boxes, ['jP ', 'ftyp', 'jp2h', 'xml ', + 'jp2c']) + + elts = jp2.box[3].xml.findall('country') + self.assertEqual(len(elts), 3) + + neighbor = elts[1].find('neighbor') + self.assertEqual(neighbor.attrib['name'], 'Malaysia') + self.assertEqual(neighbor.attrib['direction'], 'N') + + +@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") +class TestJp2kBadXmlFile(unittest.TestCase): + """Test suite for bad XML box situations""" + + @classmethod + def setUpClass(cls): + """Setup a JP2 file with a bad XML box. We only need to do this once + per class rather than once per test. + """ + jp2file = glymur.data.nemo() + with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: + cls._bad_xml_file = tfile.name + with open(jp2file, 'rb') as ifile: + # Everything up until the UUID box. + write_buffer = ifile.read(77) + tfile.write(write_buffer) + + # Write the xml box with bad xml + # Length = 28, id is 'xml '. + write_buffer = struct.pack('>I4s', int(28), b'xml ') + tfile.write(write_buffer) + + write_buffer = 'this is a test' + write_buffer = write_buffer.encode() + tfile.write(write_buffer) + + # Get the rest of the input file. + write_buffer = ifile.read() + tfile.write(write_buffer) + tfile.flush() + + @classmethod + def tearDownClass(cls): + os.unlink(cls._bad_xml_file) + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_invalid_xml_box_warning(self): + """Should warn in case of bad XML""" + with self.assertWarns(UserWarning): + Jp2k(self._bad_xml_file) + + def test_invalid_xml_box(self): + """Should be able to recover info from xml box with bad xml.""" + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2k = Jp2k(self._bad_xml_file) + + self.assertEqual(jp2k.box[3].box_id, 'xml ') + self.assertEqual(jp2k.box[3].offset, 77) + self.assertEqual(jp2k.box[3].length, 28) + self.assertIsNone(jp2k.box[3].xml) + + +@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") +class TestBadButRecoverableXmlFile(unittest.TestCase): + """Test suite for XML box that is bad, but we can still recover the XML.""" + + @classmethod + def setUpClass(cls): + """Setup a JP2 file with bad bytes preceding the XML. We only need + to do this once per class rather than once per test. + """ + jp2file = glymur.data.nemo() + with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: + cls._bad_xml_file = tfile.name + with open(jp2file, 'rb') as ifile: + # Everything up until the UUID box. + write_buffer = ifile.read(77) + tfile.write(write_buffer) + + # Write the xml box with bad xml + # Length = 64, id is 'xml '. + write_buffer = struct.pack('>I4s', int(64), b'xml ') + tfile.write(write_buffer) + + # Write out 8 bad bytes. + write_buffer = b'\x00\x00\x07\x90xml ' + tfile.write(write_buffer) + + # Write out 48 good bytes constituting the XML payload. + write_buffer = b'' + tfile.write(write_buffer) + write_buffer = b'this is a test' + tfile.write(write_buffer) + + # Get the rest of the input file. + write_buffer = ifile.read() + tfile.write(write_buffer) + tfile.flush() + + @classmethod + def tearDownClass(cls): + os.unlink(cls._bad_xml_file) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_bad_xml_box_warning(self): + """Should warn in case of bad XML""" + with self.assertWarns(UserWarning): + Jp2k(self._bad_xml_file) + + def test_recover_from_bad_xml(self): + """Should be able to recover info from xml box with bad xml.""" + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(self._bad_xml_file) + + self.assertEqual(jp2.box[3].box_id, 'xml ') + self.assertEqual(jp2.box[3].offset, 77) + self.assertEqual(jp2.box[3].length, 64) + self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()), + b'this is a test') + + diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 4f06745..0a7f509 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -55,129 +55,6 @@ def load_tests(loader, tests, ignore): return tests -@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") -@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, - "Missing openjp2 library.") -class TestJp2kBadXmlFile(unittest.TestCase): - """Test suite for bad XML box situations""" - - @classmethod - def setUpClass(cls): - """Setup a JP2 file with a bad XML box. We only need to do this once - per class rather than once per test. - """ - jp2file = pkg_resources.resource_filename(glymur.__name__, - "data/nemo.jp2") - with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: - cls._bad_xml_file = tfile.name - with open(jp2file, 'rb') as ifile: - # Everything up until the UUID box. - write_buffer = ifile.read(77) - tfile.write(write_buffer) - - # Write the xml box with bad xml - # Length = 28, id is 'xml '. - write_buffer = struct.pack('>I4s', int(28), b'xml ') - tfile.write(write_buffer) - - write_buffer = 'this is a test' - write_buffer = write_buffer.encode() - tfile.write(write_buffer) - - # Get the rest of the input file. - write_buffer = ifile.read() - tfile.write(write_buffer) - tfile.flush() - - @classmethod - def tearDownClass(cls): - os.unlink(cls._bad_xml_file) - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_invalid_xml_box_warning(self): - """Should warn in case of bad XML""" - with self.assertWarns(UserWarning): - Jp2k(self._bad_xml_file) - - def test_invalid_xml_box(self): - """Should be able to recover info from xml box with bad xml.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2k = Jp2k(self._bad_xml_file) - - self.assertEqual(jp2k.box[3].box_id, 'xml ') - self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 28) - self.assertIsNone(jp2k.box[3].xml) - - -@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") -class TestBadButRecoverableXmlFile(unittest.TestCase): - """Test suite for XML box that is bad, but we can still recover the XML.""" - - @classmethod - def setUpClass(cls): - """Setup a JP2 file with bad bytes preceding the XML. We only need - to do this once per class rather than once per test. - """ - jp2file = glymur.data.nemo() - with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: - cls._bad_xml_file = tfile.name - with open(jp2file, 'rb') as ifile: - # Everything up until the UUID box. - write_buffer = ifile.read(77) - tfile.write(write_buffer) - - # Write the xml box with bad xml - # Length = 64, id is 'xml '. - write_buffer = struct.pack('>I4s', int(64), b'xml ') - tfile.write(write_buffer) - - # Write out 8 bad bytes. - write_buffer = b'\x00\x00\x07\x90xml ' - tfile.write(write_buffer) - - # Write out 48 good bytes constituting the XML payload. - write_buffer = b'this is a test' - tfile.write(write_buffer) - - # Get the rest of the input file. - write_buffer = ifile.read() - tfile.write(write_buffer) - tfile.flush() - - @classmethod - def tearDownClass(cls): - os.unlink(cls._bad_xml_file) - - @unittest.skipIf(sys.hexversion < 0x03020000, - "Uses features introduced in 3.2.") - def test_bad_xml_box_warning(self): - """Should warn in case of bad XML""" - with self.assertWarns(UserWarning): - Jp2k(self._bad_xml_file) - - def test_recover_from_bad_xml(self): - """Should be able to recover info from xml box with bad xml.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - jp2 = Jp2k(self._bad_xml_file) - - self.assertEqual(jp2.box[3].box_id, 'xml ') - self.assertEqual(jp2.box[3].offset, 77) - self.assertEqual(jp2.box[3].length, 64) - self.assertEqual(ET.tostring(jp2.box[3].xml.getroot()), - b'this is a test') - - @unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and not OPENJP2_IS_V2_OFFICIAL, "Missing openjp2 library version 2.0+.")