<?xml version="1.0"?>
<?xml-stylesheet href="http://xslt.fourthought.com/api.xslt" type="text/xsl"?>
<article
  xmlns="http://docbook.org/docbook/xml/4.0/namespace"
  xmlns:tech="http://namespaces.fourthought.com/technical"
  xmlns:dc= "http://purl.org/dc/elements/1.1"
>
<artheader>
<title>User-Defined Extension Functions for 4XPath</title>
<author>Fourthought, Inc.</author>
</artheader>

<dc:Title>4XPath Extensions</dc:Title>
<dc:Subject>4XPath</dc:Subject>
<dc:Subject>Extension</dc:Subject>
<dc:Subject>XPAth</dc:Subject>
<dc:Description>Information on the built in extension functions in 4XPath, and how to implement your own.</dc:Description>


<sect1>
<title>Built-in Extension Functions</title>

<para>4XPath supports user-defined extension functions as specified by the XPath (and XSLT) Recommendations.  It comes with a library of convenient extension functions for a range of purposes.  These are listed here.</para>

<para>All built-in extension functions have the namespace URI 'http://xmlns.4suite.org/ext'</para>

  <tech:Function>
    <tech:Name>node-set</tech:Name>
    <tech:Desc>Convert a result-tree fragment to a node-set.  <emphasis><emphasis role="bold">Warning:</emphasis> ft:node-set is deprecated in favor of exslt:node-set.  Please see the 4Suite EXSLT documentation.</emphasis></tech:Desc>
    <tech:Arg><tech:Name>rtf</tech:Name><tech:Type>result tree fragment</tech:Type><tech:Desc>A result tree fragment such as generated by the body of an XSLT variable or parameter.</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>node set</tech:Type><tech:Desc>A node set consisting of all of the top-level nodes in the result tree fragment, including all resursive tree elements.</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>match</tech:Name>
    <tech:Desc>Match a Python regular expression against a string</tech:Desc>
    <tech:Arg><tech:Name>pattern</tech:Name><tech:Type>string</tech:Type><tech:Desc>A string representing a regular expression such as used by the Python re module..</tech:Desc></tech:Arg>
    <tech:Arg><tech:Name>arg</tech:Name><tech:Type>string</tech:Type><tech:Desc>A string to be matched against the pattern.</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>boolean</tech:Type><tech:Desc>true if the string matches, otherwise false</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>escape-url</tech:Name>
    <tech:Desc>Escape illegal characters in a URL</tech:Desc>
    <tech:Arg><tech:Name>url</tech:Name><tech:Type>string</tech:Type><tech:Desc>The URL to be escaped</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>string</tech:Type><tech:Desc>URL with all illegal characters escaped according to RFC 1738</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>iso-time</tech:Name>
    <tech:Desc>Get the current time in ISO 8601 format</tech:Desc>
    <tech:Return><tech:Type>string</tech:Type><tech:Desc>Current time in the format YYYY-MM-DD HH:MM:SS</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>evaluate</tech:Name>
    <tech:Desc>Evaluate an XPath expression at XSLT run time</tech:Desc>
    <tech:Arg><tech:Name>expr</tech:Name><tech:Type>string</tech:Type><tech:Desc>XPath expression to be parsed and evaluated using the current context.</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>boolean, number, string, or node set</tech:Type><tech:Desc>The result of evaluating the expression</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>distinct</tech:Name>
    <tech:Desc>Eliminates duplicates from a node set according to the string value of each</tech:Desc>
    <tech:Arg><tech:Name>nodeset</tech:Name><tech:Type>node set</tech:Type><tech:Desc>The node set to be processed</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>node set</tech:Type><tech:Desc>A node set from which all duplicates have been removed.  Two nodes in a node set are considered duplicates if their string values are equal.  The last node in each distinct group is the one that is retained in the final list, and the order of the node set may be disrupted.</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>split</tech:Name>
    <tech:Desc>Split a string into a node set of text nodes.</tech:Desc>
    <tech:Arg><tech:Name>arg</tech:Name><tech:Type>string</tech:Type><tech:Desc>The string to be split</tech:Desc></tech:Arg>
    <tech:Arg><tech:Name>delim</tech:Name><tech:Type>string</tech:Type><tech:Desc>The delimiter by which the string is to be split.</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>node set</tech:Type><tech:Desc>a node set of text nodes each of which represents a segment of the split string.</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>range</tech:Name>
    <tech:Desc>generate a node set of text nodes containing numbers ascending from the low value to the high value.</tech:Desc>
    <tech:Arg><tech:Name>lo</tech:Name><tech:Type>number</tech:Type><tech:Desc>The starting point for the sequence of numbers.</tech:Desc></tech:Arg>
    <tech:Arg><tech:Name>hi</tech:Name><tech:Type>number</tech:Type><tech:Desc>The ending point for the sequence of numbers.</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>node set</tech:Type><tech:Desc>A node set of text nodes, each of which represents a number value, starting from the low value to the high value, incrementing by one.</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>if</tech:Name>
    <tech:Desc>Select from two values based on a condition</tech:Desc>
    <tech:Arg><tech:Name>cond</tech:Name><tech:Type>boolean</tech:Type><tech:Desc>The condition to be checked</tech:Desc></tech:Arg>
    <tech:Arg><tech:Name>v1</tech:Name><tech:Type>boolean, number, string, or node set</tech:Type><tech:Desc>The first choice</tech:Desc></tech:Arg>
    <tech:Arg><tech:Name>v2</tech:Name><tech:Type>boolean, number, string, or node set</tech:Type><tech:Desc>The second choice</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>boolean, number, string, or node set</tech:Type><tech:Desc>The first value if the condition is true, otherwise the second value.</tech:Desc></tech:Return>
  </tech:Function>

  <tech:Function>
    <tech:Name>find</tech:Name>
    <tech:Desc>Return the index of a substring within a string</tech:Desc>
    <tech:Arg><tech:Name>outer</tech:Name><tech:Type>string</tech:Type><tech:Desc>The string to be searched</tech:Desc></tech:Arg>
    <tech:Arg><tech:Name>inner</tech:Name><tech:Type>string</tech:Type><tech:Desc>The substring to seek</tech:Desc></tech:Arg>
    <tech:Return><tech:Type>number</tech:Type><tech:Desc>The zero-based index at which the inner string is first located within the outer string.  -1 if the inner string is not found.</tech:Desc></tech:Return>
  </tech:Function>

</sect1>

<sect1>
<title>Implementing Extension Functions</title>

<para>
To define your own extension functions, define equivalent Python functions.  The module in which they are defined must have global dictionary named "ExtFunctions" mapping function names to function objects.  Function names consist of a tuple of two strings, the first being the namespace URI for the unique function, and the second being the local name.
</para>

<para>
Note that if you are using the extension function from within 4XSLT, the namespace URI must be a valid, identifying (but not necessarily addressable) URI, and in particular, it cannot be an empty string.  If you are using the extension function directly from 4XPath, the namespace URI can be the empty string.
</para>

<para>
Finally, modules containing any extension functions used must be indicated as such to the processor in one of two ways.  (1) They are listed in the environment variable "EXTMODULES".  "EXTMODULES" is a colon-separated list of modules.  (2) They are registered with 4XPath using the xml.xpath.Util.RegisterExtensionFunctions() function, which takes a list of module names.  In either case, all extension modules must should be in the "PYTHONPATH".
</para>

<para>
If your extension is called xslt-extension.py and is in the
directory
called "python", then add the variables to your environment
like this:

export PYTHONPATH=python
export EXTMODULES=xslt-extensions
</para>

<para>
For example:
</para>

<screen>
<![CDATA[
#demo.py

import time, urlparse
from xml.xpath import Conversions

def GetCurrentTime(context):
    '''available in XPath as get-curent-time()'''
    return asctime(localtime())

def HashContextName(context, maxkey):
    '''
    available in XPath as hash-context-name(maxkey),
    where maxkey is a numeric expression
    '''
    #It is a good idea to use the appropriate core function to coerce
    #arguments to the expected type
    maxkey = Conversions.NumberValue(maxkey)
    key = reduce(lambda a, b: a + b, context.node.nodeName)
    return key % maxkey

ExtFunctions = {
    ('http://spam.com', 'get-curent-time'): GetCurentTime,
    ('http://spam.com', 'hash-context-name'): HashContextName
}
]]>
</screen>

<para>
In order to use these functions, be sure that "demo" (the module name) is in the EXTMODULES environment variable, or that you call <computeroutput>xml.xpath.Util.RegisterExtensionFunctions()</computeroutput>.  If you are using them directly from 4XPath, however, you need to do one more thing: you need to set up a prefix that maps to the namespace of the functions you've defined ("http://spam.com", in this case).
</para>

<para>
You can do this by setting the "processorNss" attribute on the context you pass to the appropriate XPath method.  For instance:
</para>

<screen>
<![CDATA[
from xml.dom import ext
from xml.dom.ext.reader import Sax2
from xml.xpath import Evaluate, Util
from xml.xpath.Context import Context

try:
    doc = Sax2.FromXmlFile('myfile.xml', validate=0)
except Sax2.saxlib.SAXException, msg:
    print "SAXException caught:", msg
except Sax2.saxlib.SAXParseException, msg:
    print "SAXParseException caught:", msg
Util.IndexDocument(doc)
context = Context(doc, 1, 1, processorNss={'ext': 'http://spam.com'})
result = Evaluate("/transaction[@timestamp=ext:get-curent-time()]", doc)
Util.FreeDocumentIndex(doc)
ext.ReleaseNode(doc)
]]>
</screen>

<para>
Note that you might choose to use the empty string for the extension function namespaces.  If so, you don't need to specify the processorNss context attribute, but you might want to watch out for clashes with other extenstion function names, including the built-in library.  Again, if you plan to use an extension function from within XSLT, it must have a non-null namespace URI.
</para>

</sect1>

</article>
