# HG changeset patch
# User cmlenz
# Date 1128355417 0
# Node ID e75816cb2f452f850f5583ddcee5001e6eebee23
# Parent 372d1de2e3ecdcea6c9cdaf379577259544be978
* Add an task for applying XSLT transformations. Can use either libxslt or MSXML if available. Closes #35.
* Fix some of the unit test failures on windows.
diff --git a/bitten/build/tests/__init__.py b/bitten/build/tests/__init__.py
--- a/bitten/build/tests/__init__.py
+++ b/bitten/build/tests/__init__.py
@@ -9,13 +9,14 @@
import unittest
-from bitten.build.tests import api, config, pythontools
+from bitten.build.tests import api, config, pythontools, xmltools
def suite():
suite = unittest.TestSuite()
suite.addTest(api.suite())
suite.addTest(config.suite())
suite.addTest(pythontools.suite())
+ suite.addTest(xmltools.suite())
return suite
if __name__ == '__main__':
diff --git a/bitten/build/tests/config.py b/bitten/build/tests/config.py
--- a/bitten/build/tests/config.py
+++ b/bitten/build/tests/config.py
@@ -45,8 +45,8 @@
self.assertEqual('VERSION', config['version'])
def test_sysinfo_configfile_override(self):
- inifile = tempfile.NamedTemporaryFile(prefix='bitten_test')
- inifile.write("""
+ inifd, ininame = tempfile.mkstemp(prefix='bitten_test')
+ os.write(inifd, """
[machine]
name = MACHINE
processor = PROCESSOR
@@ -56,8 +56,8 @@
family = FAMILY
version = VERSION
""")
- inifile.seek(0)
- config = Configuration(inifile.name)
+ os.close(inifd)
+ config = Configuration(ininame)
self.assertEqual('MACHINE', config['machine'])
self.assertEqual('PROCESSOR', config['processor'])
@@ -75,14 +75,14 @@
self.assertEqual('2.3.5', config['python.version'])
def test_package_configfile(self):
- inifile = tempfile.NamedTemporaryFile(prefix='bitten_test')
- inifile.write("""
+ inifd, ininame = tempfile.mkstemp(prefix='bitten_test')
+ os.write(inifd, """
[python]
path = /usr/local/bin/python2.3
version = 2.3.5
""")
- inifile.seek(0)
- config = Configuration(inifile.name)
+ os.close(inifd)
+ config = Configuration(ininame)
self.assertEqual(True, 'python' in config.packages)
self.assertEqual('/usr/local/bin/python2.3', config['python.path'])
diff --git a/bitten/build/tests/xmltools.py b/bitten/build/tests/xmltools.py
new file mode 100644
--- /dev/null
+++ b/bitten/build/tests/xmltools.py
@@ -0,0 +1,125 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2005 Christopher Lenz
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://bitten.cmlenz.net/wiki/License.
+
+import os
+import shutil
+import tempfile
+import unittest
+
+from bitten.build import xmltools
+from bitten.recipe import Context
+from bitten.util import xmlio
+
+
+class TransformTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.basedir = os.path.realpath(tempfile.mkdtemp())
+ self.ctxt = Context(self.basedir)
+
+ def tearDown(self):
+ shutil.rmtree(self.basedir)
+
+ def test_transform_no_src(self):
+ self.assertRaises(AssertionError, xmltools.transform, self.ctxt)
+
+ def test_transform_no_dest(self):
+ self.assertRaises(AssertionError, xmltools.transform, self.ctxt,
+ src='src.xml')
+
+ def test_transform_no_stylesheet(self):
+ self.assertRaises(AssertionError, xmltools.transform, self.ctxt,
+ src='src.xml', dest='dest.xml')
+
+ def test_transform(self):
+ src_file = file(self.ctxt.resolve('src.xml'), 'w')
+ try:
+ src_file.write("""
+Document Title
+
+Section Title
+This is a test.
+This is a note.
+
+
+""")
+ finally:
+ src_file.close()
+
+ style_file = file(self.ctxt.resolve('style.xsl'), 'w')
+ try:
+ style_file.write("""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NOTE:
+
+
+""")
+ finally:
+ style_file.close()
+
+ xmltools.transform(self.ctxt, src='src.xml', dest='dest.xml',
+ stylesheet='style.xsl')
+
+ dest_file = file(self.ctxt.resolve('dest.xml'))
+ try:
+ dest = xmlio.parse(dest_file)
+ finally:
+ dest_file.close()
+
+ self.assertEqual('html', dest.name)
+ self.assertEqual('http://www.w3.org/TR/xhtml1/strict', dest.namespace)
+ children = list(dest.children())
+ self.assertEqual(2, len(children))
+ self.assertEqual('head', children[0].name)
+ head_children = list(children[0].children())
+ self.assertEqual(1, len(head_children))
+ self.assertEqual('title', head_children[0].name)
+ self.assertEqual('Document Title', head_children[0].gettext())
+ self.assertEqual('body', children[1].name)
+ body_children = list(children[1].children())
+ self.assertEqual(4, len(body_children))
+ self.assertEqual('h1', body_children[0].name)
+ self.assertEqual('Document Title', body_children[0].gettext())
+ self.assertEqual('h2', body_children[1].name)
+ self.assertEqual('Section Title', body_children[1].gettext())
+ self.assertEqual('p', body_children[2].name)
+ self.assertEqual('This is a test.', body_children[2].gettext())
+ self.assertEqual('p', body_children[3].name)
+ self.assertEqual('note', body_children[3].attr['class'])
+ self.assertEqual('This is a note.', body_children[3].gettext())
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TransformTestCase, 'test'))
+ return suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='suite')
diff --git a/bitten/build/xmltools.py b/bitten/build/xmltools.py
new file mode 100644
--- /dev/null
+++ b/bitten/build/xmltools.py
@@ -0,0 +1,86 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2005 Christopher Lenz
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://bitten.cmlenz.net/wiki/License.
+
+import logging
+import os
+
+from bitten.build import CommandLine
+from bitten.util import xmlio
+
+try:
+ import libxml2
+ import libxslt
+ have_libxslt = True
+except ImportError:
+ have_libxslt = False
+
+if not have_libxslt and os.name == 'nt':
+ try:
+ import win32com.client
+ have_msxml = True
+ except ImportError:
+ have_msxml = False
+else:
+ have_msxml = False
+
+log = logging.getLogger('bitten.build.xmltools')
+
+def transform(ctxt, src=None, dest=None, stylesheet=None):
+ """Apply an XSLT stylesheet to a source XML document."""
+ assert src, 'Missing required attribute "src"'
+ assert dest, 'Missing required attribute "dest"'
+ assert stylesheet, 'Missing required attribute "stylesheet"'
+
+ if have_libxslt:
+ log.debug('Using libxslt for XSLT transformation')
+ srcdoc, styledoc, result = None, None, None
+ try:
+ srcdoc = libxml2.parseFile(ctxt.resolve(src))
+ styledoc = libxslt.parseStylesheetFile(ctxt.resolve(stylesheet))
+ result = styledoc.applyStylesheet(srcdoc, None)
+ styledoc.saveResultToFilename(ctxt.resolve(dest), result, 0)
+ finally:
+ if styledoc:
+ styledoc.freeStylesheet()
+ if srcdoc:
+ srcdoc.freeDoc()
+ if result:
+ result.freeDoc()
+
+ elif have_msxml:
+ log.debug('Using MSXML for XSLT transformation')
+ srcdoc = win32com.client.Dispatch('MSXML2.DOMDocument.3.0')
+ if not srcdoc.load(ctxt.resolve(src)):
+ err = styledoc.parseError
+ ctxt.error('Failed to parse XML source %s: %s', src, err.reason)
+ return
+ styledoc = win32com.client.Dispatch('MSXML2.DOMDocument.3.0')
+ if not styledoc.load(ctxt.resolve(stylesheet)):
+ err = styledoc.parseError
+ ctxt.error('Failed to parse XSLT stylesheet %s: %s', stylesheet,
+ err.reason)
+ return
+ result = srcdoc.transformNode(styledoc)
+
+ # MSXML seems to always write produce the resulting XML document using
+ # UTF-16 encoding, regardless of the encoding specified in the
+ # stylesheet. For better interoperability, recode to UTF-8 here.
+ result = result.encode('utf-8').replace(' encoding="UTF-16"?>', '?>')
+
+ dest_file = file(ctxt.resolve(dest), 'w')
+ try:
+ dest_file.write(result)
+ finally:
+ dest_file.close()
+
+ else:
+ ctxt.error('No usable XSLT implementation found')
+
+ # TODO: as a last resort, try to invoke 'xsltproc' to do the
+ # transformation?
diff --git a/bitten/util/tests/archive.py b/bitten/util/tests/archive.py
--- a/bitten/util/tests/archive.py
+++ b/bitten/util/tests/archive.py
@@ -43,9 +43,12 @@
return filename
def test_index_formats(self):
- targz_path = self._create_file('snapshots/foo_r123.tar.gz')
- tarbz2_path = self._create_file('snapshots/foo_r123.tar.bz2')
- zip_path = self._create_file('snapshots/foo_r123.zip')
+ targz_path = self._create_file(os.path.join('snapshots',
+ 'foo_r123.tar.gz'))
+ tarbz2_path = self._create_file(os.path.join('snapshots',
+ 'foo_r123.tar.bz2'))
+ zip_path = self._create_file(os.path.join('snapshots',
+ 'foo_r123.zip'))
index = list(archive.index(self.env, 'foo'))
self.assertEqual(3, len(index))
assert ('123', 'gzip', targz_path) in index
@@ -53,8 +56,10 @@
assert ('123', 'zip', zip_path) in index
def test_index_revs(self):
- rev123_path = self._create_file('snapshots/foo_r123.tar.gz')
- rev124_path = self._create_file('snapshots/foo_r124.tar.gz')
+ rev123_path = self._create_file(os.path.join('snapshots',
+ 'foo_r123.tar.gz'))
+ rev124_path = self._create_file(os.path.join('snapshots',
+ 'foo_r124.tar.gz'))
index = list(archive.index(self.env, 'foo'))
self.assertEqual(2, len(index))
assert ('123', 'gzip', rev123_path) in index
@@ -65,26 +70,27 @@
self.assertEqual(0, len(index))
def test_index_prefix(self):
- path = self._create_file('snapshots/foo_r123.tar.gz')
- self._create_file('snapshots/bar_r123.tar.gz')
+ path = self._create_file(os.path.join('snapshots', 'foo_r123.tar.gz'))
+ self._create_file(os.path.join('snapshots', 'bar_r123.tar.gz'))
index = list(archive.index(self.env, 'foo'))
self.assertEqual(1, len(index))
assert ('123', 'gzip', path) in index
def test_index_no_rev(self):
- path = self._create_file('snapshots/foo_r123.tar.gz')
- self._create_file('snapshots/foo_map.tar.gz')
+ path = self._create_file(os.path.join('snapshots', 'foo_r123.tar.gz'))
+ self._create_file(os.path.join('snapshots', 'foo_map.tar.gz'))
index = list(archive.index(self.env, 'foo'))
self.assertEqual(1, len(index))
assert ('123', 'gzip', path) in index
def test_index_missing_md5sum(self):
- self._create_file('snapshots/foo_r123.tar.gz', create_md5sum=False)
+ self._create_file(os.path.join('snapshots', 'foo_r123.tar.gz'),
+ create_md5sum=False)
index = list(archive.index(self.env, 'foo'))
self.assertEqual(0, len(index))
def test_index_nonmatching_md5sum(self):
- path = self._create_file('snapshots/foo_r123.tar.gz',
+ path = self._create_file(os.path.join('snapshots', 'foo_r123.tar.gz'),
create_md5sum=False)
md5sum = md5.new('Foo bar')
md5sum_file = file(path + '.md5', 'w')
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -51,7 +51,8 @@
NS + 'python#exec = bitten.build.pythontools:exec_',
NS + 'python#pylint = bitten.build.pythontools:pylint',
NS + 'python#trace = bitten.build.pythontools:trace',
- NS + 'python#unittest = bitten.build.pythontools:unittest'
+ NS + 'python#unittest = bitten.build.pythontools:unittest',
+ NS + 'x#transform = bitten.build.xmltools:transform'
]
},
test_suite='bitten.tests.suite', zip_safe=True