def characters(self, data): """ Callback interface for SAX. """ parent_state = self._element_state_stack[-1] # verify that the current element can have text children try: next = parent_state.validation[content_model.TEXT_NODE] except KeyError: # If the parent can have element children, but not text nodes, # ignore pure whitespace nodes. This clarification is from # XSLT 2.0 [3.4] Whitespace Stripping. # e.g. xsl:stylesheet, xsl:apply-templates, xsl:choose #self._debug_validation(content_model.TEXT_NODE) #if (content_model.EMPTY in parent_state.validation or # not isspace(data)): if 1: if len(data) > 10: data = data[:10] + '...' raise XsltStaticError(XsltError.ILLEGAL_TEXT_CHILD, parent_state.node, data=data, element=parent_state.node.nodeName) #self._debug_validation(content_model.TEXT_NODE) else: # update validation parent_state.validation = next node = xslt_text(self._root, data) parent_state.node.appendChild(node) return
def _handle_standard_attr(self, state, instance, name, value): if name == 'extension-element-prefixes': # a whitespace separated list of prefixes ext = state.extensionNamespaces = state.extensionNamespaces.copy() out = state.outputNamespaces = state.outputNamespaces.copy() for prefix in value: # add the namespace URI to the set of extension namespaces try: uri = instance.namespaces[prefix] except KeyError: raise XsltStaticError(XsltError.UNDEFINED_PREFIX, instance, prefix=prefix or '#default') ext[uri] = True # remove all matching namespace URIs for output_prefix, output_uri in out.items(): if output_uri == uri: del out[output_prefix] elif name == 'exclude-result-prefixes': # a whitespace separated list of prefixes out = state.outputNamespaces = state.outputNamespaces.copy() for prefix in value: try: uri = instance.namespaces[prefix] except KeyError: raise XsltStaticError(XsltError.UNDEFINED_PREFIX, instance, prefix=prefix or '#default') # remove all matching namespace URIs for output_prefix, output_uri in out.items(): if output_uri == uri: del out[output_prefix] elif name == 'version': # XSLT Spec 2.5 - Forwards-Compatible Processing state.forwardsCompatible = (value != 1.0) instance._version = value else: if '-' in name: name = name.replace('-', '_') instance.__dict__['_' + name] = value return
def _handle_result_element_attr(self, state, instance, elementName, attributeName, value): try: attr_info = _RESULT_ELEMENT_XSL_ATTRS[attributeName] except KeyError: raise XsltStaticError(XsltError.ILLEGAL_XSL_NAMESPACE_ATTR, instance, attribute=attributeName, element=elementName) value = attr_info.prepare(instance, value) self._handle_standard_attr(state, instance, attributeName, value) return
def _combine_stylesheet(self, element, is_import): href = element._href try: new_source = self._input_source.resolve(href, self._input_source.uri) except (OSError, IriError): # FIXME: create special inputsource for 4xslt command-line #for uri in self._alt_base_uris: # try: # new_href = self._input_source.getUriResolver().normalize(href, uri) # #Do we need to figure out a way to pass the hint here? # new_source = self._input_source.factory.fromUri(new_href) # break # except (OSError, IriError): # pass #else: raise XsltStaticError(XsltError.INCLUDE_NOT_FOUND, element, uri=href, base=self._locator.getSystemId()) # XSLT Spec 2.6.1, Detect circular references in stylesheets # Note, it is NOT an error to include/import the same stylesheet # multiple times, rather that it may lead to duplicate definitions # which are handled regardless (variables, params, templates, ...) if new_source.uri in self._visited_stylesheet_uris: raise XsltStaticError(XsltError.CIRCULAR_INCLUDE, element, uri=new_source.uri) self.parse(new_source) self._import_index += is_import # Always update the precedence as the included stylesheet may have # contained imports thus increasing the import precedence. self._stylesheet.import_precedence = self._import_index return
def instantiate(self, context): params = {} for param, name, select in self._params: context.instruction, context.namespaces = param, param.namespaces params[name] = select.evaluate(context) if self._select: context.instruction, context.namespaces = self, self.namespaces try: nodes = self._select.evaluate_as_nodeset(context) except TypeError: raise raise XsltStaticError(XsltError.INVALID_APPLY_TEMPLATES_SELECT, self) else: nodes = context.node.xml_children # Process the selected nodes using `self._mode` context.transform.apply_templates(context, nodes, self._mode, params) return
def endElementNS(self, expandedName, qualifiedName, _literal_element=literal_element.literal_element, _variable_element=variable_elements.variable_element): """ Callback interface for SAX. """ stack = self._element_state_stack state = stack[-1] del stack[-1] parent_state = stack[-1] element = state.node # ---------------------------------------------------------- # verify that this element has all required content try: state.validation[content_model.END_ELEMENT] except KeyError: if expandedName == (XSL_NAMESPACE, u'choose'): raise XsltStaticError(XsltError.MISSING_REQUIRED_ELEMENT, element, element=element.nodeName, child='xsl:when') raise # ---------------------------------------------------------- # setup variable context if state.localVariables is not parent_state.localVariables: # add context save/restore nodes binding_stack = [] node = push_variables_node(self._root, binding_stack) element.insertChild(0, node) node = pop_variables_node(self._root, binding_stack) element.appendChild(node) # ---------------------------------------------------------- # finalize the children for this element #element.children = tuple(state.nodes) #for child in element.children: # if child.doesSetup: #s child.setup() del state # ---------------------------------------------------------- # update parent state parent_node = parent_state.node if self._stylesheet is None and parent_node is element.root: # a literal result element as stylesheet assert isinstance(element, _literal_element), element try: version = element._version except AttributeError: raise XsltStaticError(XsltError.LITERAL_RESULT_MISSING_VERSION, element) # Reset the root's validation as it has already seen an element. parent_state.validation = _XSLT_ROOT_VALIDATION # FIXME: use the prefix from the document for the XSL namespace stylesheet = (XSL_NAMESPACE, u'stylesheet') self.startElementNS(stylesheet, u'xsl:stylesheet', {(None, u'version'): version}) template = (XSL_NAMESPACE, u'template') self.startElementNS(template, u'xsl:template', {(None, u'match'): u'/'}) # make this element the template's content # Note, this MUST index the stack as the stack has changed # due to the startElementNS() calls. stack[-1].node.appendChild(element) self.endElementNS(template, u'xsl:template') self.endElementNS(stylesheet, u'xsl:stylesheet') return parent_node.appendChild(element) if isinstance(element, _variable_element): name = element._name if parent_node is self._stylesheet: # global variables if name in self._global_vars: existing = self._global_vars[name] if self._import_index > existing: self._global_vars[name] = self._import_index elif self._import_index == existing: raise XsltStaticError( XsltError.DUPLICATE_TOP_LEVEL_VAR, element, variable=name) else: self._global_vars[name] = self._import_index else: # local variables # it is safe to ignore import precedence here local_vars = parent_state.localVariables if name in local_vars: raise XsltStaticError(XsltError.ILLEGAL_SHADOWING, element, variable=name) # Copy on use if local_vars is stack[-2].localVariables: local_vars = local_vars.copy() parent_state.localVariables = local_vars local_vars[name] = True return
def startElementNS( self, expandedName, qualifiedName, attribs, _literal_element=literal_element.literal_element, _element_classes=ELEMENT_CLASSES, _element_cache={}, ): """ Callback interface for SAX. """ parent_state = self._element_state_stack[-1] state = parse_state(**parent_state.__dict__) self._element_state_stack.append(state) # ---------------------------------------------------------- # update in-scope namespaces if self._new_namespaces: d = state.currentNamespaces = state.currentNamespaces.copy() d.update(self._new_namespaces) d = state.outputNamespaces = state.outputNamespaces.copy() for prefix, uri in self._new_namespaces.iteritems(): if uri not in (XML_NAMESPACE, XSL_NAMESPACE): d[prefix] = uri # reset for next element self._new_namespaces = {} # ---------------------------------------------------------- # get the class defining this element namespace, local = expandedName xsl_class = ext_class = None if namespace == XSL_NAMESPACE: try: xsl_class, validation, validation_token, legal_attrs = \ _element_cache[local] except KeyError: # We need to try to import (and cache) it try: xsl_class = _element_classes[local] except KeyError: if not state.forwardsCompatible: raise XsltStaticError(XsltError.XSLT_ILLEGAL_ELEMENT, parent_state.node, element=local) xsl_class = fallback_elements.undefined_xslt_element validation_token = content_model.RESULT_ELEMENT else: validation_token = expandedName validation = xsl_class.content_model.compile() legal_attrs = xsl_class.attribute_types.items() _element_cache[local] = (xsl_class, validation, validation_token, legal_attrs) elif namespace in state.extensionNamespaces: try: ext_class, validation, legal_attrs = \ self._extelement_cache[expandedName] except KeyError: try: ext_class = self._extelements[expandedName] except KeyError: ext_class = fallback_elements.undefined_extension_element validation = ext_class.content_model.compile() legal_attrs = ext_class.attribute_types if legal_attrs is not None: legal_attrs = legal_attrs.items() self._extelement_cache[expandedName] = (ext_class, validation, legal_attrs) validation_token = content_model.RESULT_ELEMENT else: validation = _LITERAL_ELEMENT_VALIDATION validation_token = content_model.RESULT_ELEMENT state.validation = validation # ---------------------------------------------------------- # verify that this element can be declared here try: next = parent_state.validation[validation_token] except KeyError: #self._debug_validation(expandedName) # ignore whatever elements are defined within an undefined # element as an exception will occur when/if this element # is actually instantiated if not isinstance(parent_state.node, fallback_elements.undefined_extension_element): raise XsltStaticError(XsltError.ILLEGAL_ELEMENT_CHILD, parent_state.node, element=qualifiedName) else: # save this state for next go round parent_state.validation = next # ---------------------------------------------------------- # create the instance defining this element klass = (xsl_class or ext_class or _literal_element) state.node = instance = klass(self._root, expandedName, qualifiedName, state.currentNamespaces) instance.baseUri = self._locator.getSystemId() instance.lineNumber = self._locator.getLineNumber() instance.columnNumber = self._locator.getColumnNumber() instance.import_precedence = self._import_index if xsl_class: # -- XSLT element -------------------------------- # Handle attributes in the null-namespace standand_attributes = local in ('stylesheet', 'transform') inst_dict = instance.__dict__ for attr_name, attr_info in legal_attrs: attr_expanded = (None, attr_name) if attr_expanded in attribs: value = attribs[attr_expanded] del attribs[attr_expanded] elif attr_info.required: raise XsltStaticError(XsltError.MISSING_REQUIRED_ATTRIBUTE, instance, element=qualifiedName, attribute=attr_name) else: value = None try: value = attr_info.prepare(instance, value) except XsltError, e: #raise self._mutate_exception(e, qualifiedName) raise if standand_attributes: self._stylesheet = instance self._handle_standard_attr(state, instance, attr_name, value) else: if '-' in attr_name: attr_name = attr_name.replace('-', '_') inst_dict['_' + attr_name] = value if attribs: # Process attributes with a namespace-uri and check for # any illegal attributes in the null-namespace for expanded in attribs: attr_ns, attr_name = expanded if attr_ns is None: if not state.forwardsCompatible: raise XsltStaticError( XsltError.ILLEGAL_NULL_NAMESPACE_ATTR, instance, attribute=attr_name, element=qualifiedName) else: instance.setAttribute(attr_ns, attr_name, attribs[expanded]) # XSLT Spec 2.6 - Combining Stylesheets if local in ('import', 'include'): self._combine_stylesheet(instance, (local == 'import'))
class stylesheet_reader(object): """ This class can be used to read, from a variety of sources, a stylesheet and all its included and imported stylesheets, building from them a single, compact representation of an XSLT stylesheet tree (an Ft.Xml.Xslt.Stylesheet.Stylesheet object). This is done with the most efficient parsing method available, and avoids creating a Domlette document for each document it reads. """ # runtime instance variables _input_source = None _locator = None _stylesheet = None _root = None def __init__(self): self._import_index = 0 self._global_vars = {} self._visited_stylesheet_uris = {} self._document_state_stack = [] self._element_state_stack = [] self._extelements = {} self._extelements.update(exslt.extension_elements) self._extelements.update(extensions.extension_elements) self._extelement_cache = {} return def reset(self): self._root = None self._import_index = 0 self._global_vars = {} self._visited_stylesheet_uris = {} self._document_state_stack = [] self._element_state_stack = [] return def addExtensionElementMapping(self, elementMapping): """ Add a mapping of extension element names to classes to the existing mapping of extension elements. This should only be used for standalone uses of this class. The only known standalone use for this class is for creating compiled stylesheets. The benefits of compiled stylesheets are now so minor that this use case may also disappear and then so will this function. You have been warned. """ self._extelements.update(elementMapping) for name in elementMapping: if name in self._extelement_cache: del self._extelement_cache[name] return # -- ContentHandler interface -------------------------------------- def setDocumentLocator(self, locator): """ Callback interface for SAX. """ # Save the current document state for nested parsing (inclusions) document_state = (self._locator, self._stylesheet) self._document_state_stack.append(document_state) self._locator = locator self._stylesheet = None return def startDocument(self): """ Callback interface for SAX. """ # Our root is always a document # We use a document for this because of error checking and # because we explicitly pass ownerDocument to the nodes as # they are created document_uri = self._locator.getSystemId() root = xslt_root(document_uri) if not self._root: self._root = root self._element_state_stack.append( parse_state( node=root, validation=_XSLT_ROOT_VALIDATION, localVariables={}, forwardsCompatible=False, currentNamespaces={ 'xml': XML_NAMESPACE, None: None }, extensionNamespaces={}, outputNamespaces={}, )) # for recursive include checks for xsl:include/xsl:import self._visited_stylesheet_uris[document_uri] = True # namespaces added for the next element self._new_namespaces = {} return def endDocument(self): """ Callback interface for SAX. """ stack = self._element_state_stack state = stack[-1] del stack[-1] root = state.node # ---------------------------------------------------------- # remove URI from recursive inclusion checking del self._visited_stylesheet_uris[root.baseUri] # ---------------------------------------------------------- # finalize the children for the document #root.children = tuple(state.nodes) # ---------------------------------------------------------- # finalize the stylesheet AST if stack: # An xsl:import or xsl:include # Merge the top-level elements into the "parent" stylesheet # IMPLEMENTATION NOTE: stack[-1] is the import/include element, # stack[-2] is the "parent" stylesheet stack[-2].node._merge(self._stylesheet) #parent_node = stack[-2].node #for child in self._stylesheet.children: # child.parent = parent_node else: # A top-most stylesheet stylesheet = self._root.stylesheet if stylesheet is not self._stylesheet: # An additional stylesheet (e.g., an <?xml-stylesheet ...?>); # treat it as an xsl:import into the "master" stylesheet. stylesheet.reset() # Always update the precedence from the included stylesheet # because it may have contained imports thus increasing its # import precedence. self._import_index += 1 stylesheet.import_precedence = self._import_index # Merge the top-level elements into the "master" stylesheet stylesheet._merge(self._stylesheet) #stylesheet.children += self._stylesheet.children #for child in self._stylesheet.children: # child.parent = stylesheet else: # Prepare for a possible subsequent parse. self._import_index += 1 # Prepare the "master" stylesheet stylesheet.setup() document_state = self._document_state_stack[-1] del self._document_state_stack[-1] self._locator, self._stylesheet = document_state return def startPrefixMapping(self, prefix, uri): """ Callback interface for SAX. """ self._new_namespaces[prefix] = uri return def startElementNS( self, expandedName, qualifiedName, attribs, _literal_element=literal_element.literal_element, _element_classes=ELEMENT_CLASSES, _element_cache={}, ): """ Callback interface for SAX. """ parent_state = self._element_state_stack[-1] state = parse_state(**parent_state.__dict__) self._element_state_stack.append(state) # ---------------------------------------------------------- # update in-scope namespaces if self._new_namespaces: d = state.currentNamespaces = state.currentNamespaces.copy() d.update(self._new_namespaces) d = state.outputNamespaces = state.outputNamespaces.copy() for prefix, uri in self._new_namespaces.iteritems(): if uri not in (XML_NAMESPACE, XSL_NAMESPACE): d[prefix] = uri # reset for next element self._new_namespaces = {} # ---------------------------------------------------------- # get the class defining this element namespace, local = expandedName xsl_class = ext_class = None if namespace == XSL_NAMESPACE: try: xsl_class, validation, validation_token, legal_attrs = \ _element_cache[local] except KeyError: # We need to try to import (and cache) it try: xsl_class = _element_classes[local] except KeyError: if not state.forwardsCompatible: raise XsltStaticError(XsltError.XSLT_ILLEGAL_ELEMENT, parent_state.node, element=local) xsl_class = fallback_elements.undefined_xslt_element validation_token = content_model.RESULT_ELEMENT else: validation_token = expandedName validation = xsl_class.content_model.compile() legal_attrs = xsl_class.attribute_types.items() _element_cache[local] = (xsl_class, validation, validation_token, legal_attrs) elif namespace in state.extensionNamespaces: try: ext_class, validation, legal_attrs = \ self._extelement_cache[expandedName] except KeyError: try: ext_class = self._extelements[expandedName] except KeyError: ext_class = fallback_elements.undefined_extension_element validation = ext_class.content_model.compile() legal_attrs = ext_class.attribute_types if legal_attrs is not None: legal_attrs = legal_attrs.items() self._extelement_cache[expandedName] = (ext_class, validation, legal_attrs) validation_token = content_model.RESULT_ELEMENT else: validation = _LITERAL_ELEMENT_VALIDATION validation_token = content_model.RESULT_ELEMENT state.validation = validation # ---------------------------------------------------------- # verify that this element can be declared here try: next = parent_state.validation[validation_token] except KeyError: #self._debug_validation(expandedName) # ignore whatever elements are defined within an undefined # element as an exception will occur when/if this element # is actually instantiated if not isinstance(parent_state.node, fallback_elements.undefined_extension_element): raise XsltStaticError(XsltError.ILLEGAL_ELEMENT_CHILD, parent_state.node, element=qualifiedName) else: # save this state for next go round parent_state.validation = next # ---------------------------------------------------------- # create the instance defining this element klass = (xsl_class or ext_class or _literal_element) state.node = instance = klass(self._root, expandedName, qualifiedName, state.currentNamespaces) instance.baseUri = self._locator.getSystemId() instance.lineNumber = self._locator.getLineNumber() instance.columnNumber = self._locator.getColumnNumber() instance.import_precedence = self._import_index if xsl_class: # -- XSLT element -------------------------------- # Handle attributes in the null-namespace standand_attributes = local in ('stylesheet', 'transform') inst_dict = instance.__dict__ for attr_name, attr_info in legal_attrs: attr_expanded = (None, attr_name) if attr_expanded in attribs: value = attribs[attr_expanded] del attribs[attr_expanded] elif attr_info.required: raise XsltStaticError(XsltError.MISSING_REQUIRED_ATTRIBUTE, instance, element=qualifiedName, attribute=attr_name) else: value = None try: value = attr_info.prepare(instance, value) except XsltError, e: #raise self._mutate_exception(e, qualifiedName) raise if standand_attributes: self._stylesheet = instance self._handle_standard_attr(state, instance, attr_name, value) else: if '-' in attr_name: attr_name = attr_name.replace('-', '_') inst_dict['_' + attr_name] = value if attribs: # Process attributes with a namespace-uri and check for # any illegal attributes in the null-namespace for expanded in attribs: attr_ns, attr_name = expanded if attr_ns is None: if not state.forwardsCompatible: raise XsltStaticError( XsltError.ILLEGAL_NULL_NAMESPACE_ATTR, instance, attribute=attr_name, element=qualifiedName) else: instance.setAttribute(attr_ns, attr_name, attribs[expanded]) # XSLT Spec 2.6 - Combining Stylesheets if local in ('import', 'include'): self._combine_stylesheet(instance, (local == 'import')) elif ext_class: # -- extension element ------------------------- validate_attributes = (legal_attrs is not None) if validate_attributes: # Handle attributes in the null-namespace inst_dict = instance.__dict__ for attr_name, attr_info in legal_attrs: attr_expanded = (None, attr_name) if attr_expanded in attribs: value = attribs[attr_expanded] del attribs[attr_expanded] elif attr_info.required: raise XsltStaticError( XsltError.MISSING_REQUIRED_ATTRIBUTE, instance, element=qualifiedName, attribute=attr_name) else: value = None try: value = attr_info.prepare(instance, value) except XsltError, e: #raise self._mutate_exception(e, qualifiedName) raise if '-' in attr_name: attr_name = attr_name.replace('-', '_') inst_dict['_' + attr_name] = value
#raise self._mutate_exception(e, qualifiedName) raise if '-' in attr_name: attr_name = attr_name.replace('-', '_') inst_dict['_' + attr_name] = value # Process attributes with a namespace-uri and check for # any illegal attributes in the null-namespace if attribs: for expanded in attribs: attr_ns, attr_name = expanded value = attribs[expanded] if validate_attributes and attr_ns is None: raise XsltStaticError( XsltError.ILLEGAL_NULL_NAMESPACE_ATTR, instance, attribute=attr_name, element=qualifiedName) elif attr_ns == XSL_NAMESPACE: self._handle_result_element_attr( state, instance, qualifiedName, attr_name, value) else: instance.setAttribute(attr_ns, attr_name, value) else: # -- literal result element ------------------------------ output_attrs = [] for expanded in attribs: attr_ns, attr_local = expanded value = attribs[expanded] if attr_ns == XSL_NAMESPACE: self._handle_result_element_attr(state, instance, qualifiedName, attr_local,