Source code for publiforge.lib.processor.leprisme.publiset

"""Publiset management."""

from os.path import join, basename, splitext, normpath, relpath
from copy import deepcopy

from lxml import etree

from ...i18n import _
from ...xml import XML_NS, load_xml

PUBLIDOC_VERSION = '1.0'
PUBLISET_VERSION = '1.0'


# =============================================================================
[docs]class Publiset(object): """Class for Publiset management.""" # ------------------------------------------------------------------------- def __init__(self, processor, base_path): """Constructor method. :param processor: (:class:`~.lib.processor.leprisme.Processor` object) Processor object on which it depends. :param base_path: (string) Base path for files in publiset. """ self._processor = processor self._base_path = base_path self._pi_fid = False self._pi_source = False self._params = { 'output': '"%s/"' % processor.output, 'processor': '"%s/"' % join(processor.build.path, 'Processor')} for name, value in processor.build.processing[ 'variables'].items(): if isinstance(value, bool): self._params[name] = str(int(value)) elif isinstance(value, int): self._params[name] = str(value) else: self._params[name] = '"%s"' % value # -------------------------------------------------------------------------
[docs] def fullname(self, file_elt): """Find the full path of a file from a file tag. :param file_elt: (etree.Element object) File element. :return: (string) Full path name. """ elt = file_elt while elt is not None and elt.get('path') is None: elt = elt.getparent() if elt is None or elt.get('path') is None: return join(self._base_path, file_elt.text) return normpath(join( self._base_path, elt.get('path'), file_elt.text))
# -------------------------------------------------------------------------
[docs] def compose(self, filename, set_root): """Compose an XML document from a publiset XML composition. :param filename: (string) Name of the composition file. :param set_root: (:class:`lxml.etree.Element` instance) ``composition`` element. :return: (etree.ElementTree object) Document tree or ``None``. """ # Document root element doc_root = self._doc_element( set_root, 'publidoc', {'version': PUBLIDOC_VERSION}) self._pi_fid = set_root.get('pi-fid') self._pi_source = set_root.get('pi-source') # Browse structure for elt in set_root.xpath('*|processing-instruction()'): if not self._append(elt, doc_root): return None doc_root = etree.ElementTree(doc_root) # Validate if self._processor.config('Input', 'validate') == 'true': doc_root = load_xml( filename, self._processor.relaxngs, etree.tostring(doc_root)) if isinstance(doc_root, basestring): self._processor.build.stopped(doc_root, 'a_error') return None return doc_root
# -------------------------------------------------------------------------
[docs] @classmethod def create(cls, element): """Create an empty ``publiset`` document and fill it with ``element``. :param element: (:class:`lxml.etree.Element` instance) Main element. :return: (etree.ElementTree) """ root = etree.Element('publiset', version=PUBLISET_VERSION) root.append(element) return etree.ElementTree(root)
# ------------------------------------------------------------------------- def _append(self, set_current, doc_current): """Append ``set_current`` to ``doc_current`` element, converting tag and attribute names. :param set_current: (:class:`lxml.etree.Element` instance) Publiset element. :param doc_current: (:class:`lxml.etree.Element` instance) Target document element. :return: (boolean) """ # pylint: disable = R0911 # Processing instruction if not isinstance(set_current.tag, basestring): doc_current.append(set_current) return True # File if set_current.tag == 'file': return self._append_file(set_current, doc_current) # Browse doc_elt = self._doc_element(set_current) for set_elt in set_current.xpath('*|processing-instruction()'): if not self._append(set_elt, doc_elt): return False if set_current.get('transform') is None: doc_current.append(doc_elt) return True # Transform xslfile = join(self._processor.build.path, 'Processor', 'Xsl', set_current.get('transform')) try: transform = etree.XSLT(etree.parse(xslfile)) except (IOError, etree.XSLTParseError, etree.XMLSyntaxError) as err: self._processor.build.stopped( str(err).replace(self._processor.build.path, '..')) return False try: for doc_elt in transform(doc_elt)\ .xpath('/*|/processing-instruction()'): doc_current.append(doc_elt) except (etree.XSLTApplyError, AssertionError) as err: self._processor.build.stopped(err, 'a_error') return False return True # ------------------------------------------------------------------------- def _append_file(self, file_elt, doc_current): """Append file content as child of ``doc_current``. :param file_elt: (:class:`lxml.etree.Element` instance) File element. :param doc_current: (:class:`lxml.etree.Element` instance) Target document element. :return: (boolean) """ # Load file # pylint: disable = E1103, R0911 if file_elt.text is None: self._processor.build.stopped(_('Empty <file/> tag.'), 'a_warn') return True relaxngs = self._processor.relaxngs \ if self._processor.config('Input', 'validate') == 'true' else None filename = self.fullname(file_elt) if not filename.startswith(self._processor.build.data_path): self._processor.build.stopped(_( '${f}: file outside storage area', {'f': basename(filename)})) return False tree = load_xml(filename, relaxngs) if isinstance(tree, basestring): self._processor.build.stopped(tree, 'a_error') return False # PI if self._pi_fid: doc_current.append( etree.ProcessingInstruction( 'fid', splitext(basename(filename))[0])) if self._pi_source: doc_current.append( etree.ProcessingInstruction( 'source', relpath(filename, self._processor.build.data_path))) # How to compose?... set_elt = file_elt while set_elt is not None and set_elt.get('xslt') is None \ and set_elt.get('xpath') is None: set_elt = set_elt.getparent() # ...copy if set_elt is None: self._append_file_element( doc_current, deepcopy(tree.getroot()), file_elt.get('argument'), file_elt.get('mode')) # ...XSL transformation elif set_elt.get('xslt'): return self._append_file_xslt( filename, tree, set_elt.get('xslt'), doc_current, file_elt.get('argument'), file_elt.get('mode')) # ...XPath else: try: for child in tree.xpath(set_elt.get('xpath')): self._append_file_element( doc_current, child, file_elt.get('argument'), file_elt.get('mode')) except etree.XPathEvalError as err: self._processor.build.stopped('XPath: %s' % err, 'a_error') return False return True # ------------------------------------------------------------------------- @classmethod def _append_file_element( cls, doc_current, child, argument=None, mode=None): """Append a file element as child of ``doc_current`` with, possibly, a ``<?argument ?>`` processing instruction with the content of ``argument`` and a ``<?mode ?>`` processing instruction with the content of ``mode``. :param doc_current: (:class:`lxml.etree.Element` instance) Target document element. :param child: (:class:`lxml.etree.Element` instance) Element to append. :param argument: (string, optional) Argument for a processing instruction. :param mode: (string, optional) Mode for a processing instruction. """ if child.tag is not etree.Comment and child.tag is not etree.PI: if mode is not None: child.insert( 0, etree.ProcessingInstruction('mode', mode.strip())) if argument is not None: child.insert( 0, etree.ProcessingInstruction( 'argument', argument.strip())) doc_current.append(child) # ------------------------------------------------------------------------- def _append_file_xslt(self, filename, tree, xsl_file, doc_current, argument=None, mode=None): """Append a XSL transformation file element as child of ``doc_current`` with, possibly, a ``<?argument ?>`` processing instruction and a ``<?mode ?>`` processing instruction. :param filename: (string) Full path to the current file. :param tree: (:class:`lxml.etree.ElementTree` instance) DOM of the file to process. :param xsl_file: (string) Name of the XSL file. :param argument: (string, optional) Argument for a processing instruction. :param mode: (string, optional) Mode for a processing instruction. :return: (boolean) """ # pylint: disable = too-many-arguments xsl_file = join( self._processor.build.path, 'Processor', 'Xsl', xsl_file) try: xslt = etree.XSLT(etree.parse(xsl_file)) except (IOError, etree.XSLTParseError, etree.XMLSyntaxError) as error: self._processor.build.stopped( str(error).replace(self._processor.build.path, '..')) return False self._params['fid'] = '"%s"' % splitext(basename(filename))[0] try: for child in xslt(tree, **self._params)\ .xpath('/*|/comment()|/processing-instruction()'): self._append_file_element(doc_current, child, argument, mode) except AssertionError: return True except etree.XSLTApplyError as error: self._processor.build.stopped(error, 'a_error') return False return True # ------------------------------------------------------------------------- @classmethod def _doc_element(cls, set_elt, default_tag=None, default_atts=None): """Create a document element from a set element according to its ``as`` and ``attributes`` attributes. :param set_elt: (:class:`lxml.etree.Element` instance) Source element :param default_tag: (string) If ``default_tag`` is None and ``as`` attribute doesn't exist, the ``set_elt`` name is used as tag name. :param default_atts: (dictionary) If ``default_atts`` is None and ``attributes`` attribute doesn't exist, ``set_elt`` attributes are copied. :return: (:class:`lxml.etree.ElementTree`) """ # Tag if default_tag is None: default_tag = set_elt.tag doc_elt = etree.Element(set_elt.get('as', default_tag)) # Attributes atts = set_elt.get('attributes', '').split() if atts: atts = dict([(i.split('=')[0], i.split('=')[1]) for i in atts]) else: atts = default_atts or set_elt.attrib for att in ('argument', 'mode'): if set_elt.get(att) is not None: doc_elt.append(etree.ProcessingInstruction( att, set_elt.get(att))) for att, value in atts.items(): if att not in ('as', 'attributes', 'transform', 'path', 'xslt', 'xpath', 'argument', 'mode'): doc_elt.set(att.replace('xml:', XML_NS), value) # Text doc_elt.text = set_elt.text doc_elt.tail = set_elt.tail return doc_elt