# XPath queries

4Suite provides an XPath processing engine, compliant with the W3C XPath 1.0 specification. This query engine is accessible through Ft.Xml.XPath.

## The quickest option

If you are using Domlette, as described above, the quickest and easiest way to use the XPath facility in 4Suite is the xpath() method, which any Domlette Node supports:

    from Ft.Xml.Domlette import NonvalidatingReader
    doc = NonvalidatingReader.parseString("<spam>eggs<a/><a/></spam>")
    doc2 = NonvalidatingReader.parseString("<spam>eggs<eggs n='1'> and ham</eggs></spam>")
    print doc.xpath(u'(//a)[1]')
    print doc.xpath(u'string(/spam)')
    print doc2.xpath(u'string(//eggs/@n)')

The line

    print doc.xpath(u'(//a)[1]')

Is actually a shortcut for the following more involved construct, which is described in detail in the next section:

    from Ft.Xml.XPath import Evaluate
    print Evaluate(u'(//a)[1]', contextNode=doc)

This example prints three lines. The first line shows a string representation of a list containing a single element. As we see from this line, an XPath selection of nodes returns a Python list. In this case, it is a list containing a single element—the first element with a local name of a, which has no attributes and no children. The second line shows the correct string value of the selected spam element, and the third line shows the correct string value of the n attribute.

    [<Element at 0xb7d10bb4: name u'a', 0 attributes, 0 children>]
    eggs
    1

## Type mappings

4Suite XPath functions return results with Python types that depend on the XPath data model type of the query result. The following list shows how the five XPath result types (String, number, boolean, node-set and object) are mapped to Python types:

- XPath string: Python unicode type

- XPath number: Python float type (int or long also accepted), or instance of Ft.Lib.number.nan (for NaN) or Ft.Lib.number.inf (for Infinity)

- XPath boolean: Ft.Lib.boolean instance

- XPath node-set: Python list of Domlette nodes, in document order, with no duplicates

- XPath foreign object: any other Python object (you will very rarely encounter this case)

## Advanced use

XPath expressions can refer to both variables and qualified names (QNames) that must be defined by the environment that is executing the XPath expression. This section describes how to use these advanced features of XPath using the 4Suite interface.

4Suite's XPath implementation uses a Domlette node as the context node for XPath operations. The following example demonstrates the use of XPath to extract content from an XML document. The document must be parsed before Xpath can be used to access it. The following example parses the XML document and explicitly sets up an XPath context to run an XPath query.

    XML = """
    <ham>
    <eggs n='1'/>
    This is the string content with <em>emphasized text</em> text
    </ham>"""

    from Ft.Xml import Parse
    from Ft.Xml.XPath.Context import Context
    from Ft.Xml.XPath import Evaluate

    doc = Parse(XML)
    ctx = Context(doc)
    nodes = Evaluate(u'//em', ctx)

    # The return value, a node set, comes back as a Python list of nodes
    # which may be accessed using an iterator
    for n in nodes:
        # print dir(n)
        print n.tagName
        print n.firstChild.nodeValue

XPath always requires a context for execution; a common XPath context is the root of the target document, such as we did in the above example. Think about an XPath query being executed from some location in an XML document. This location in the document is a necessary component of using XPath.

There is more to an XPath context than just the context node, but if your needs are as straightforward as that of the above example, there is an abbreviated version of the Evaluate method for this purpose. For example, the following fragment is equivalent to the two lines creating a context and evaluating the expression in the above example.

    # No need to create a context object
    Evaluate(u'//em', contextNode=doc)

If your source document uses XML Namespaces you will likely need to use QNames in your XPath expressions. For this to work, you'll need to introduce namespace mappings into your XPath context. For example, if the elements of our XML document above are in an XML namespace, then we must set up our context slightly differently.

    XML = """<ham xmlns="http://example.com/ns#">
    <eggs n='1'/>
    This is the string content with <em type='bold'>emphasized Namespaced Text</em> text
    </ham>"""

    from Ft.Xml import Parse
    from Ft.Xml.XPath.Context import Context
    from Ft.Xml.XPath import Evaluate

    NSS = {u'ex': u'http://example.com/ns#'}
    doc = Parse(XML)
    ctx = Context(doc, processorNss=NSS)
    nodes = Evaluate(u'//ex:em', ctx)
    for n in nodes:
        # print dir(n)
        print n.tagName
        print n.firstChild.nodeValue

You define XPath namespace prefixes through a Python dictionary (`NSS` in the above example) which maps these prefixes, such as '`ex`' in the above example, to the appropriate namespace URI, such as '`http://example.com/ns#`' in the above example. This prefix mapping is added to your XPath context using the processorNss parameter to the `Context` function.

In a similar way, you can also pass in variable bindings which may be used as values later in your XPath expressions. In this case, however, variables are Python tuples containing the namespace URI and local name of the variable.

    ctx = Context(node, varBindings=
      {(EMPTY_NAMESPACE, u'date'): u'2003-06-20'})
    Evaluate('event[@date = $date]', context=ctx)

This creates a variable in the default namespace named 'date', with a value of '`2003-06-20`'; this is then used for comparison with the date attribute in the Xpath expression.

XPath variables are Qnames, so you pass in variable names as namespace/local name tuples. The values can be numbers, unicode objects or boolean objects:

    from Ft.Xml.XPath import boolean
    ctx = Context(node, varBindings={(EMPTY_NAMESPACE, u'test'): boolean.true})

This sets the variable 'test' to the boolean value true (remember that this is for the XPath environment, not the Python one), and again this may be used as in any XSLT stylesheet.

If you only want a value once, you may of course still use string constants, as in

    nodes=Evaluate(u'//testPrefix:em[@type="bold"]',ctx)

Note the quotes used? These must be balanced, hence the literal value uses double quotes.

## Reusing parsed XPath queries

Sometimes you want to re-use an XPath expression and namespace mapping multiple times, for efficiency and convenience. The following example shows an example of this:

    from Ft.Xml.XPath.Context import Context
    from Ft.Xml.XPath import Compile, Evaluate
    from Ft.Xml import Parse

    DOCS = ["<spam xmlns='http://spam.com'>eggs</spam>",
            "<spam xmlns='http://spam.com'>grail</spam>",
            "<spam xmlns='http://spam.com'>nicht</spam>",
           ]

    # Pre-compile for efficiency and convenience
    expr = Compile(u"/a:spam[contains(., 'i')]")
    ctx = Context(None, processorNss={u"a": u"http://spam.com"})

    i = 1
    for doc in DOCS:
        doc = NonvalidatingReader.parseString(doc.encode('UTF-8'),
                                              "http://spam.com/base")
        retval = Evaluate(expr, doc, ctx)
        if len(retval):
            print "Document", i, "meets our criteria"
        i += 1

Which should display:

    Document 2 meets our criteria
    Document 3 meets our criteria

## Migration from PyXML's XPath

There is a usable XPath module in PyXML (warning: PyXML's XSLT implementation is not usable: use 4Suite if you need XSLT), but there are a lot of updates and improvements in the XPath library version in 4Suite.

If you are familiar with PyXML, you may have used a different form of imports to load in XPath and XSLT features. The imports are different under 4Suite.

Usage example:

1. PyXML usage (do not use with 4Suite):

       import xml.xslt
       import xml.xpath

2. 4Suite usage (use these imports):

       import Ft.Xml.XPath
       import Ft.Xml.Xslt