def Key(context, qname, keyList): """ Implementation of key(). The first argument specifies the name of the key. When the second argument to the key function is of type node-set, then the result is the union of the result of applying the key function to the string value of each of the nodes in the argument node-set. When the second argument to key is of any other type, the argument is converted to a string as if by a call to the string function; it returns a node-set containing the nodes in the same document as the context node that have a value for the named key equal to this string. """ qname = Conversions.StringValue(qname) if not qname: raise XsltRuntimeException(Error.INVALID_QNAME_ARGUMENT, context.currentInstruction, qname) split_name = context.expandQName(Conversions.StringValue(qname)) doc = context.node.rootNode try: keys_for_context_doc = context.processor.keys[doc] requested_key = keys_for_context_doc[split_name] except KeyError: sheet = context.processor.stylesheet sheet.updateKey(doc, split_name, context.processor) keys_for_context_doc = context.processor.keys[doc] requested_key = keys_for_context_doc[split_name] result = [] if not isinstance(keyList, NodesetType): keyList = [keyList] for key in keyList: key = Conversions.StringValue(key) result.extend(requested_key.get(key, [])) return result
def Tokenize(context, string, delimiters='\t\n\r '): """ The str:tokenize function splits up a string and returns a node set of 'token' elements, each containing one token from the string. The first argument is the string to be tokenized. The second argument is a string consisting of a number of characters. Each character in this string is taken as a delimiting character. The string given by the first argument is split at any occurrence of any of these characters. """ string = Conversions.StringValue(string) if delimiters: delimiters = Conversions.StringValue(delimiters) tokens = re.split('[%s]' % delimiters, string) else: tokens = string processor = context.processor processor.pushResultTree(context.currentInstruction.baseUri) try: for token in tokens: processor.writer.startElement(u'token') processor.writer.text(token) processor.writer.endElement(u'token') finally: rtf = processor.popResult() return rtf.childNodes
def Replace(context, source, pattern, flags, repl): """ The regexp:replace function replaces the parts of a string that match a regular expression with another string. The first argument is the string to be matched and replaced. The second argument is a regular expression that follows the Javascript regular expression syntax. The fourth argument is the string to replace the matched parts of the string. The third argument is a string consisting of character flags to be used by the match. If a character is present then that flag is true. The flags are: g: global replace - all occurrences of the regular expression in the string are replaced. If this character is not present, then only the first occurrence of the regular expression is replaced. i: case insensitive - the regular expression is treated as case insensitive. If this character is not present, then the regular expression is case sensitive. """ source = Conversions.StringValue(source) pattern = Conversions.StringValue(pattern) flags = Conversions.StringValue(flags) repl = Conversions.StringValue(repl) regexp = re.compile(pattern, 'i' in flags and re.IGNORECASE or 0) # a count of zero means replace all in RE.sub() return regexp.sub(repl, source, 'g' not in flags)
def DecodeUri(context, uri, encoding=u'UTF-8'): """ The str:decode-uri function decodes a percent-encoded string, such as one would find in a URI. """ uri = Conversions.StringValue(uri) encoding = Conversions.StringValue(encoding) try: decoder = codecs.lookup(encoding)[1] except LookupError: # Unsupported encoding return u'' def repl(match, decoder=decoder): # Remove the leading '%' sequence = match.group()[1:] # There may be multiple encoded characters that are required # to produce a single Unicode character. ordinals = sequence.split('%') characters = [chr(int(ordinal, 16)) for ordinal in ordinals] # Ignore any invalid sequences in this encoding return decoder(''.join(characters), 'ignore')[0] return re.sub('(?:%[0-9a-fA-F]{2})+', repl, uri)
def Align(context, target, padding, alignment=''): """ The str:align function aligns a string within another string. See http://exslt.org/str/functions/align/str.align.html for further explanation. """ target = Conversions.StringValue(target) padding = Conversions.StringValue(padding) alignment = alignment and Conversions.StringValue(alignment) # If the target string is longer than the padding string, then it is # truncated to be the same length as the padding string and returned. if len(target) > len(padding): return target[:len(padding)] # If no third argument is given or if it is not one of 'left', 'right' # or 'center', then it defaults to left alignment. if alignment == 'right': result = padding[:-len(target)] + target elif alignment == 'center': # With center alignment, the range of characters replaced by the target # string is in the middle of the padding string, such that either the # number of unreplaced characters on either side of the range is the # same or there is one less on the left than there is on the right. left = (len(padding) - len(target)) / 2 right = left + len(target) result = padding[:left] + target + padding[right:] else: result = target + padding[len(target):] return result
def Split(context, string, pattern=u' '): """ The str:split function splits up a string and returns a node set of token elements, each containing one token from the string. The first argument is the string to be split. The second argument is a pattern string (default=' '). The string given by the first argument is split at any occurrence of this pattern. An empty string pattern will result in a split on every character in the string. """ string = Conversions.StringValue(string) pattern = Conversions.StringValue(pattern) processor = context.processor processor.pushResultTree(context.currentInstruction.baseUri) try: if pattern: for token in string.split(pattern): processor.writer.startElement(u'token') processor.writer.text(token) processor.writer.endElement(u'token') else: for ch in string: processor.writer.startElement(u'token') processor.writer.text(ch) processor.writer.endElement(u'token') finally: rtf = processor.popResult() return rtf.childNodes
def pipp_thumbnail(context, src, width, height): ctx = context.processor.extensionParams[(NAMESPACE, 'context')] image_name = ctx.abs_in_path(Conversions.StringValue(src)) ctx.add_depends(image_name[len(ctx.in_root):]) thumb_name = re.sub('(\.\w+)$', '_thumb\g<1>', Conversions.StringValue(src)) if width: width = int(Conversions.NumberValue(width)) if height: height = int(Conversions.NumberValue(height)) img = Image.open(image_name) w, h = img.size if height and not width: width = int(w * height / h) if width and not height: height = int(h * width / w) img = img.resize((width, height)) img.save(ctx.abs_out_path(ctx.abs_in_path(thumb_name))) #-- # Add image to cache using fake inroot name, so width/height functions work #-- images[ctx.abs_in_path(thumb_name)] = img return thumb_name
def pipp_code(context, src, code, lexer, docss): ctx = context.processor.extensionParams[(NAMESPACE, 'context')] src = Conversions.StringValue(src) if src: abs_src = ctx.abs_in_path(src) ctx.add_depends(abs_src[len(ctx.in_root):]) fname = os.path.basename(src) code = open(abs_src).read() else: fname = 'inline-code' code = Conversions.StringValue(code) lexer = Conversions.StringValue(lexer) if lexer: lexer = get_lexer_by_name(lexer) elif src: lexer = get_lexer_for_filename(fname) else: raise Exception( 'The lexer must be explicitly specified for inline code blocks') formatter = HtmlFormatter(cssclass="source") result = highlight(code, lexer, formatter) if Conversions.StringValue(docss) == '1': result = '<link rel="stylesheet" href="%s.css"/>' % fname + result css = open(ctx.abs_out_path(ctx.abs_in_path(fname + '.css')), 'w') css.write(formatter.get_style_defs()) css.close() return result
def pipp_export(context, name, value): ctx = context.processor.extensionParams[(NAMESPACE, 'context')] new_node = ctx.state_doc.createElementNS(EMPTY_NAMESPACE, Conversions.StringValue(name)) new_node.appendChild( ctx.state_doc.createTextNode(Conversions.StringValue(value))) ctx.exports_node.appendChild(new_node)
def evaluate(self, context): """Returns a boolean""" left = self._left.evaluate(context) right = self._right.evaluate(context) if isinstance(left, Types.NodesetType) or \ isinstance(right, Types.NodesetType): return _nodeset_compare(self._cmp, left, right) # From XPath 1.0 Section 3.4: # order for equality expressions when neither is a node-set # 1. either is boolean, both are converted as if by boolean() # 2. either is number, both are converted as if by number() # otherwise, both are converted as if by string() if isinstance(left, Types.BooleanType): right = Conversions.BooleanValue(right) elif isinstance(right, Types.BooleanType): left = Conversions.BooleanValue(left) elif isinstance(left, Types.NumberType): right = Conversions.NumberValue(right) elif isinstance(right, Types.NumberType): left = Conversions.NumberValue(left) else: left = Conversions.StringValue(left) right = Conversions.StringValue(right) return self._cmp(left, right) and boolean.true or boolean.false
def pipp_link(context, link): """Record a link""" ctx = context.processor.extensionParams[(NAMESPACE, 'context')] new_node = ctx.state_doc.createElementNS(EMPTY_NAMESPACE, Conversions.StringValue('link')) new_node.appendChild( ctx.state_doc.createTextNode(Conversions.StringValue(link))) ctx.links_node.appendChild(new_node)
def EndsWith(context, outer, inner): """ Returns true if the string given in the first argument ends with the substring given in the second argument. """ outer = Conversions.StringValue(outer) inner = Conversions.StringValue(inner) return outer.endswith(inner) and boolean.true or boolean.false
def Match(context, source, pattern, flags=''): """ The regexp:match function lets you get hold of the substrings of the string passed as the first argument that match the captured parts of the regular expression passed as the second argument. The second argument is a regular expression that follows the Javascript regular expression syntax. The third argument is a string consisting of character flags to be used by the match. If a character is present then that flag is true. The flags are: g: global match - the submatches from all the matches in the string are returned. If this character is not present, then only the submatches from the first match in the string are returned. i: case insensitive - the regular expression is treated as case insensitive. If this character is not present, then the regular expression is case sensitive. The regexp:match function returns a node set of 'match' elements, each of whose string value is equal to a portion of the first argument string that was captured by the regular expression. If the match is not global, the first match element has a value equal to the portion of the string matched by the entire regular expression. """ source = Conversions.StringValue(source) pattern = Conversions.StringValue(pattern) flags = flags and Conversions.StringValue(flags) regexp = re.compile(pattern, 'i' in flags and re.IGNORECASE or 0) match = regexp.search(source) if match is None: return [] processor = context.processor processor.pushResultTree(context.currentInstruction.baseUri) try: if 'g' in flags: # find all matches in the source while match: processor.writer.startElement(u'match') # return everything that matched the pattern processor.writer.text(match.group()) processor.writer.endElement(u'match') match = regexp.search(source, match.end()) else: # the first 'match' element contains entire matched text all = [match.group()] groups = match.groups() groups and all.extend(list(groups)) for match in all: processor.writer.startElement(u'match') match and processor.writer.text(match) processor.writer.endElement(u'match') finally: rtf = processor.popResult() return rtf.childNodes
def ResolvePath(context, base, rel): """ Resolves a Posix-style path, such as the path portion of a URL, against a base. Similar to f:resolve-url, but allows the base to be just a path, not necessarily a full URL. """ base = Conversions.StringValue(base) rel = Conversions.StringValue(rel) return Uri.BaseJoin(base, rel)
def Contains(context, outer, inner): """Function: <string> contains(<string>, <string>)""" if not isinstance(outer, XPathStringType): outer = Conversions.StringValue(outer) if not isinstance(inner, XPathStringType): inner = Conversions.StringValue(inner) if not inner: return boolean.true return outer.find(inner) >= 0 and boolean.true or boolean.false
def StartsWith(context, outer, inner): """Function: <string> starts-with(<string>, <string>)""" if not isinstance(outer, XPathStringType): outer = Conversions.StringValue(outer) if not isinstance(inner, XPathStringType): inner = Conversions.StringValue(inner) if not inner: return boolean.true return outer[:len(inner)] == inner and boolean.true or boolean.false
def Lookup(context, name, key): """ f:lookup() queries an index as defined by f:create-index. """ name = Conversions.StringValue(name) key = Conversions.StringValue(key) processor = context.processor indices = processor.extensionParams.get((FT_EXT_NAMESPACE, 'indices'), {}) index = indices.get(name, {}) value = index.get(key, []) return value
def ResolveUrl(context, base, rel): """ Returns the relative URL ref given in the second argument resolved against the base given in the first argument. In case of URI processing error an empty string is returned """ base = Conversions.StringValue(base) rel = Conversions.StringValue(rel) try: return Uri.Absolutize(rel, base) except Uri.UriException: return u''
def SubstringAfter(context, outer, inner): """Function: <string> substring-after(<string>, <string>)""" if not isinstance(outer, XPathStringType): outer = Conversions.StringValue(outer) if not isinstance(inner, XPathStringType): inner = Conversions.StringValue(inner) if not inner: return u'' index = outer.find(inner) if index == -1: return u'' return outer[index + len(inner):]
def pipp_import_join(context, name, join_str): ctx = context.processor.extensionParams[(NAMESPACE, 'context')] name = Conversions.StringValue(name) cur_doc = ctx.state_node values = [] while cur_doc: ctx.add_edepends(cur_doc.getAttributeNS(EMPTY_NAMESPACE, 'src'), name) nodes = cur_doc.xpath("exports/*[name()='%s']" % name.replace("'", "")) if nodes: values.insert(0, get_text(nodes[0])) cur_doc = cur_doc.parentNode.parentNode return Conversions.StringValue(join_str).join(values)
def ParseDate(context, date, format=None): """ This function is similar to EXSLT's date:parse-date() except that it uses Python rather than Java conventions for the date formatting. """ import time date = Conversions.StringValue(date) format = Conversions.StringValue(format) time_tuple = time.strptime(format) #perhaps add some variants for missing time tuple values? str_time = time.strftime("%Y-%m-%dT%H:%M:%S", time_tuple) return unicode(str_time, 'us-ascii', errors='replace')
def Replace(context, old, new, arg=None): """ Returns the third argument string, which defaults to the string-value of the context node, with occurrences of the substring given in the first argument replaced by the string given in the second argument. See also: EXSLT's str:replace() """ if not arg: arg = context.node arg = Conversions.StringValue(arg) old = Conversions.StringValue(old) new = Conversions.StringValue(new) return arg.replace(old, new)
def StrFTime(context, format, date=None): """ Returns the given ISO 8601 UTC date-time formatted according to the given format string as would be used by Python's time.strftime(). If no date-time string is given, the current time is used. """ format = Conversions.StringValue(format) if date is not None: date = Conversions.StringValue(date) time_str = time.strftime(format, time.strptime(date, '%Y-%m-%dT%H:%M:%SZ')) else: time_str = time.strftime(format) return unicode(time_str, 'us-ascii', errors='replace')
def Seconds(context, string=None): """ The date:seconds function returns the number of seconds specified by the argument string. If no argument is given, then the current local date/time, as returned by date:date-time is used as a default argument. Implements version 1. """ if string is None: string = str(_DateTime.now()) else: string = Conversions.StringValue(string) try: if 'P' in string: # its a duration duration = _Duration.parse(string) else: # its a dateTime dateTime = _DateTime.parse( string, ('dateTime', 'date', 'gYearMonth', 'gYear')) duration = _difference(_EPOCH, dateTime) except ValueError: return number.nan # The number of years and months must both be equal to zero if duration.years or duration.months: return number.nan # Convert the duration to just seconds seconds = (duration.days * 86400 + duration.hours * 3600 + duration.minutes * 60 + duration.seconds) if duration.negative: seconds *= -1 return seconds
def EscapeXml(context, text): """ Returns the given string with XML markup characters "&", "<" and ">" escaped as "&", "<" and ">", respectively. """ from xml.sax.saxutils import escape return escape(Conversions.StringValue(text))
def Lang(context, lang): """Function: <boolean> lang(<string>)""" lang = Conversions.StringValue(lang).lower() node = context.node while node.parentNode: for attr in node.attributes.values(): # Search for xml:lang attribute if (attr.localName == 'lang' and attr.namespaceURI == XML_NAMESPACE): value = attr.nodeValue.lower() # Exact match (PrimaryPart and possible SubPart) if value == lang: return boolean.true # Just PrimaryPart (ignore '-' SubPart) index = value.find('-') if index != -1 and value[:index] == lang: return boolean.true # Language doesn't match return boolean.false # Continue to next ancestor node = node.parentNode # No xml:lang declarations found return boolean.false
def Id(context, object_): """Function: <node-set> id(<object>)""" if not isinstance(object_, NodesetType): st = Conversions.StringValue(object_) id_list = st.split() else: id_list = [Conversions.StringValue(n) for n in object_] id_list = Set.Unique(id_list) doc = context.node.rootNode nodeset = [] for id in id_list: element = doc.getElementById(id) if element: nodeset.append(element) return nodeset
def pipp_child(context, file_name): ctx = context.processor.extensionParams[(NAMESPACE, 'context')] file_name = ctx.abs_in_path(Conversions.StringValue(file_name)) \ [len(ctx.in_root):] new_node = ctx.state_doc.createElementNS(EMPTY_NAMESPACE, 'page') new_node.setAttributeNS(EMPTY_NAMESPACE, 'src', file_name) ctx.children_node.appendChild(new_node)
def instantiate(self, context, processor): doc = context.node.rootNode if self._raw.evaluate(context): processor.xslMessage(repr(context.varBindings)) else: from Ft.Xml.XPath.XPathTypes import g_xpathPrimitiveTypes from Ft.Xml.Xslt.CopyOfElement import CopyNode writer = processor.writer writer.startElement(u'zz:VarDump', RESERVED_NAMESPACE) for k, v in context.varBindings.items(): writer.startElement(u'zz:Var', RESERVED_NAMESPACE) #FIXME: should try to join back prefix to var name writer.attribute(u'name', k[1], EMPTY_NAMESPACE) if isinstance(v, list): # NOTE - this must be before the primitive check due to # the fact that a node-set is a primitive type for node in v: if node.nodeType == Node.ATTRIBUTE_NODE: processor.writer.comment( u"Attribute: %s=%s" % (node.nodeName, node.value)) else: CopyNode(processor, node) elif type(v) in g_xpathPrimitiveTypes: writer.text(Conversions.StringValue(v)) elif hasattr(v, 'nodeType'): CopyNode(processor, v) writer.endElement(u'zz:Var', RESERVED_NAMESPACE) writer.endElement(u'zz:VarDump', RESERVED_NAMESPACE) return
def pipp_map_view(context, xslt_file): ctx = context.processor.extensionParams[(NAMESPACE, 'context')] #-- # Create the XSLT processor object. For efficiency there is a cache of these. #-- xslt_file = ctx.abs_in_path(Conversions.StringValue(xslt_file)) ctx.add_depends(xslt_file[len(ctx.in_root):]) processor = processors.get(xslt_file) if not processor: processor = Processor.Processor() processor.registerExtensionModules(['pipp_xslt']) processor.appendStylesheet( InputSource.DefaultFactory.fromString( open(xslt_file).read(), xslt_file)) processor.extensionParams[(NAMESPACE, 'context')] = ctx #-- # Run the processor against state.xml and return the output. # If successful, store the processor object in a cache #-- input = InputSource.DefaultFactory.fromUri(OsPathToUri(ctx.state_xml)) output = processor.run(input) processors[xslt_file] = processor return output