Skip to content

glro/xptr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

2. 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).

2. 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.

3. 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.

3. 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]")

4. 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);
    });

About

Pure JavaScript XPointer Implementation

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published