When you are building a non-trivial parser by using RelaxNGCC, you'll often find it useful to make a sort of "context" information available to all the parsing code. Such context information can include an error handler to report problems, a global dictionary where you keep ID values, a stack that tracks xml:base attribute, or a map that keeps track of in-scope namespace bindings.
In this tutorial, I will explain how you can extend NGCCRuntime to meet those needs.
The primary role of NGCCRuntime is to receive SAX events and choreograph NGCCHandler-derived classes, which are generated from the schema. For this purpose, it implements XMLFilter. In turn, each NGCCHandler class has the runtime field, which points back to the instance of NGCCRuntime in charge.
RelaxNGCC allows applications to use its own class instead of NGCCRuntime, provided that it extends NGCCRuntime. There you define your own methods and fields, and you can have your NGCCHandlers use those methods and fields.
Let's see a very simple example of this. First, you define your own class by extending NGCCRuntime.
package org.acme.parser; import org.acme.parser.gen.NGCCRuntime; public class MyRuntime extends NGCCRuntime { public void sayHello() { System.out.println("Hello, world!"); } }
It's usually a good idea to use a dedicated package for the NGCC-generated files (because you can easily delete all the generated files), so I assumed that the generated code will go to org.acme.parser.gen package.
Then, you put a cc:runtime-type attribute to your grammar, so that the compiler can generate a reference to the runtime by using a proper type.
<element name="root" xmlns="http://relaxng.org/ns/structure/1.0" xmlns:cc="http://www.xml.gr.jp/xmlns/relaxngcc" cc:runtime-type="org.acme.parser.Runtime" > <text/> <cc:java> runtime.sayHello(); </cc:java> </element>
Each handler class will be generated with the runtime field declared as the specified type (when you don't specify cc:runtime-type, it will still be declared but as the default NGCCRuntime type.) Thus to access the runtime, you simply need to use this field, as shown in the example.
Default NGCCRuntime comes with many functionalities that are useful to many applications.
new org.xml.sax.helpers.LocatorImpl(getLocator())
)
<element name="root" xmlns="http://relaxng.org/ns/structure/1.0" xmlns:cc="http://www.xml.gr.jp/xmlns/relaxngcc" > <cc:java-body> private org.dom4j.io.SAXContentHandler domBuilder = new org.dom4j.io.SAXContentHandler(); </cc:java-body> <zeroOrMore> <choice> <element name="child"> <text/> </element> <group> <element> <anyName> <except> <nsName/> </except> </anyName> runtime.redirectSubtree(domBuilder,uri,localName,qname); <empty/> </element> Document result = domBuilder.getDocument(); </choice> </zeroOrMore> </element>Three variables uri,localName,qname are variables that defined locally. For now, just think of it as a magic. The key here is to define RELAX NG as if the content of that element is empty. By the time your code after the </element is executed, the whole sub-tree is completely fed to the specified ContentHandler.
Unless you know what you are doing, it is not recommended to override methods like startElement or characters, because there is a delay between the time a runtime receives those events and the time a handler receives those events.
Writing a lengthy Java code inside schema is painful and makes it less legible. This is yet another reason why you want to use this mechanism. For a real world example, see the source code of RELAX NG parser of RelaxNGCC itself. Also, check out the javadoc of NGCCRuntime for details.