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 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 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 Constant(context, name, precision): """ The math:constant function returns the specified constant to a set precision. """ name = Conversions.StringValue(name) if not CONSTANTS.has_key(name): return number.nan precision = Conversions.NumberValue(precision) return float('%0.*f' % (int(precision), CONSTANTS[name]))
def check_uniqueness(self, context, nodes): '''available in XPath as check-uniqueness''' hash = {} for n in nodes: h = self.nodeUID(n) if (hash.has_key(h)): return Conversions.BooleanValue(0) hash[h] = 1 return Conversions.BooleanValue(1)
def ATan2(context, y, x): """ The math:atan2 function returns the angle ( in radians ) from the X axis to a point (y,x). """ x = Conversions.NumberValue(x) y = Conversions.NumberValue(y) try: return math.atan2(y, x) except ValueError: return number.nan
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 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 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, relational=True) left = Conversions.NumberValue(left) right = Conversions.NumberValue(right) return self._cmp(left, right) and boolean.true or boolean.false
def Wrap(context, text, width): """ f:wrap() returns a string with the text reflowed so that each line fits within the given width. Existing linefeeds are preserved, but spaces are considered inter-word separators that can be collapsed. To reflow without preserving existing linefeeds, strip them first, e.g. with translate(text, ' ', ''). http://lists.fourthought.com/pipermail/4suite-dev/2002-December/000878.html """ s = Conversions.StringValue(text) width = Conversions.NumberValue(width) return LineWrap(s, width)
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 evaluate(self, context): '''Returns a number''' if self._leftLit: lrt = self._left else: lrt = self._left.evaluate(context) lrt = Conversions.NumberValue(lrt) if self._rightLit: rrt = self._right else: rrt = self._right.evaluate(context) rrt = Conversions.NumberValue(rrt) return lrt + (rrt * self._sign)
def Padding(context, length, chars=None): """ The str:padding function creates a padding string of a certain length. The second argument gives a string to be used to create the padding. This string is repeated as many times as is necessary to create a string of the length specified by the first argument; if the string is more than a character long, it may have to be truncated to produce the required length. If no second argument is specified, it defaults to a space (' '). """ length = int(Conversions.NumberValue(length)) chars = chars and Conversions.StringValue(chars) or u' ' return (chars * length)[:length]
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 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 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 __init__(self, sign, left, right): self._sign = sign self._leftLit = 0 self._rightLit = 0 if isinstance(left, ParsedLiteralExpr): self._leftLit = 1 self._left = Conversions.NumberValue(left.evaluate(None)) else: self._left = left if isinstance(right, ParsedLiteralExpr): self._rightLit = 1 self._right = Conversions.NumberValue(right.evaluate(None)) else: self._right = right return
def StringLength(context, st=None): """Function: <number> string-length(<string>?)""" if st is None: st = context.node if not isinstance(st, XPathStringType): st = Conversions.StringValue(st) return float(len(st))
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 Normalize(context, st=None): """Function: <string> normalize-space(<string>?)""" if st is None: st = context.node if not isinstance(st, XPathStringType): st = Conversions.StringValue(st) return u' '.join(st.split())
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 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
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)