Home > xptr

xptr

Xptr is a project mainly written in JavaScript, based on the ISC license.

Pure JavaScript XPointer Implementation

xptr is a Pure Javascript XPointer Implementation

  1. Motivation

xptr aims to be a complete Javascript implementation of the XPointer standard. XPointer is very similar to XPath, but extends it by providing support for 2 new types: points & ranges. Points and ranges basically allow you to reference not just DOM Nodes, but actual points or ranges of text within a DOM.

Most browser provide some sort of selection range support; XPointer is just an extension of XPath to support these types of ranges. Being able to convert between browser selection ranges and XPointer is one of the goals of this project. This would allow user-selected ranges to be persisted in a standard format, which could be used by server-side or other client-side code.

Some examples of ways this could be used:

  • Client-side, browser agnostic support of the Annotea standard (this is the primary end-goal of this project)

  • Client-side library that parses the local anchor part of a URL as an XPointer expression and scrolls the page to this point (robust anchors)

  • Persist user text selections across requests in a standard way

  1. Yet Another XPath Implementation

XPointer supports different schemes for identifying points and ranges. The most versatile and robust is an extension XPath. XPointer extends XPath by adding 2 new types to XPath (point and range), and several new functions. In order to implement XPointer then, this project requires an extensible version of XPath that will allow new types, functions and contexts. Current browser support for XPath does not allow for much in the way of extending XPath, either by adding functions or types. This meant the focus would have to shift to a Javascript implementation.

A sub-project of xptr, then, is to create an implementation of XPath that focuses on extensibility. This includes adding new types, overriding existing functions for new types, as well as creating new functions that use existing types and new types as arguments and return values. The sub-project is entitled xpath for now (yep, pretty lame).

  1. Current Status

2.1 Lligen LL(1) Parser Generator

Both xptr and xpath required a parser, so a simple, pure Javascript, LL(1) parser generator was implemented that uses a simple DSL to define grammars in Javascript. As of right now, the entire parser is recreated from the Grammar each time the xpath library is loaded. The next step for the parser is to create a mechanism to write static versions of the parser as a Javascript file, with the parse table and everything already filled out. This will save start-up time and download size (since the parser generator won't be included any more).

2.2 xpath Extensible Javascript XPath Implementation

The focus right now is to get the extensible implementation of XPath workinging correctly. As of now, the parser is largely complete. It can handle all-but-one axes, most functions, all node type tests, and all expressions and predicates. Namespace support will be the next major feature added. There are also no unit tests as of right now and this needs to be rectified ASAP.

2.3 xptr Javascript XPointer Implementation

Work on the XPointer implementation has not started yet. Once the XPath implementation is stable and has been tested, work will start on xptr.

  1. Building the Library

By default, the library is split across many Javascript files. You could include all of these in your web site if you wish, but it is better to build the merged and minified versions and use those. To build the library, simply run:

make

In the top level directory. You will probably need to edit the Makefile and change the location of the YUI Compressor JAR file. This will create a new directory, build/, with 2 files in it. One is just a big merged version of all the Javascript files. The other is a minified version of the merged file.

  1. Using the Library

As of right now, you can run some non-trivial XPath expressions. The interface is very basic right now and does not attempt to follow the DOM 3 XPath specification of the APIs (though support for this may come later). The easiest way to use it is through the xpath function:

xpath("//DIV[@class='section']") xpath("(//OL | //UL)/LI[position() = last() or position() = 1]") xpath("id('content')/*[self::H2 or self::H3]")

  1. Extending XPath with xpath

To deal with extensions, xpath provides simple mechanisms to define new types and function libraries. All basic operations (+,-,*,div,mod,mul,|,=,!=,<,>,<=,

=,etc.) are equivalent to calling user-defined functions such as add(), modulus(), less-than(), equals(), etc. This allows extensions to easily add support for basic operations to their types as well. The easiest way to add a new function is by extending the core function library.

Let's say we wished to add a new function, has-class, that would return true if the current node had a specific class, given as a string. Though this is simple, XPath does not support such a test easily. For example,

xpath("//*[contains(@class, 'selected')]")

would return all nodes that have the class selected, but it would also return all nodes with the class 'not-selected'! Luckily, extending xpath to support such a function is simple:

xpath.core.library.define("has-class",               /* Function name */
                      xpath.core.types.BOOLEAN,      /* Return type */
                      [ xpath.core.types.STRING ],   /* Argument types */
                      function(klass) {              /* Implementation */
    var node = this.dot(); // Returns the current context node
    return new RegExp("(^|s)" + klass + "($|s)").test(node.className);
});

We can then use this function in an XPath expression. For example:

xpath("//*[has-class('selected')]")

returns all nodes that have class 'selected'. The nice part is that xpath will handle type-checking for you. If you ran xpath("//*[has-class(id('foo'))]") then you would get an error telling you that has-class can't be called with a node-set argument. Of course, you can also override "has-class" to support node-sets:

xpath.core.library.define("has-class",               /* Function name */
                      xpath.core.types.BOOLEAN,      /* Return type */
                      [ xpath.core.types.NODE_SET ], /* Argument types */
                      function(nodeset) {            /* Implementation */
    var node = this.dot(); // Returns the current context node
    var klass = xpath.core.stringValue(nodeSet.get(0));
    return new RegExp("(^|s)" + klass + "($|s)").test(node.className);
});
Previous:android-imf-ext