libxml2, XmlReader, and RELAX NG on OS X

Update: If you are looking to use the RELAX NG features of libxml2 on OS X, I highly recommend that you download at latest version: 2.6.26. Earlier versions appear to have some RELAX NG bugs. Specifically, 2.2.16 ships with Tiger, and has some important RELAX NG problems. Download the 2.6.26 .framework binary provided by Steve Ball and include it in your Xcode project instead.

Thanks to libxml folks for an awesome, free toolkit, and to Steve for the framework help.

A couple of weeks ago, I set out to find an open source RELAX NG implemenation available for Mac OS X and suitable for using in a Cocoa application. If you haven’t heard of RELAX NG, it’s a really really great XML schema langauge that is meant to be a successor to DTD and is much, much simpler than W3C XML Schema. It’s bad ass.

So, back to my search for an impl… Knowing that libxml2 ships with OS X since Panther, I checked their site first. Ding! libxml2 includes a C and Python API for RELAX NG validation. Although I love playing with shiny toys like Python, the C API struck me as a much better fit for a Cocoa app.

I couldn’t find another C-based RELAX NG API… Xerces-C++ and Expat don’t appear to have support for RNG.

That left Java implementations (there are several, of course) and Mono which seems to have an experimental RNG implementation in their SVN repo. Since Apple’s JavaBridge is deprecated, and I wasn’t in the mood for learning JNI, Java was out, and that left Mono… I’ve recently gotten Dumbarton Bridge examples up and running, which is a really cool Cocoa/ObjC <-> Mono/C# bridge. But I really thought that keeping the code in native C had too many advantages over mixing in a non-native platform and language like Mono/C# into a Cocoa app.

So libxml2’s C API was what I settled on. Just one problem… Todd don’t program good in C. And unfortunately, I couldn’t find any real documentation on the libxml2 C API for RNG. Not a single example.

So I resolved that once I figured out libxml2’s RELAX NG API, I’d post an example for future googlers.

AFAIK, the only access to RNG validation in libxml2 is via it’s .NET-inspired XmlReader pull-parser API. libxml2’s SAX2 API doesn’t appear to offer RNG validation.

So, anyhow, here’s the result of a couple of days of experimentation. The code and comments should be pretty self-explanatory.

Without further ado, RELAX NG validation using libxml2 on OS X:


#import <Foundation/Foundation.h>
#import <libxml/xmlreader.h>
#import <libxml/relaxng.h>

// Don't know what this handles, can't get it to fire.
void readerErr(void *arg, const char *msg,
             xmlParserSeverities severity,
             xmlTextReaderLocatorPtr locator)
{
    int line = xmlTextReaderLocatorLineNumber(locator);
    NSLog(@"Some kinda error! %s, severity: %i, line: %i", msg, severity, line);
}

// handles well-formedness errors in instance document
// and handles validity errors in instance doc
void structErr(void *userData, xmlErrorPtr error)
{
    const char *msg = error->message;
    int line = error->line;
    int level = error->level;

    NSLog(@"Instance doc well-formedness error or validity error, level: %i", level);
    NSLog(@"message: = %s", msg);
    NSLog(@"line: = %i", line);
}

//xmlHTMLValidityWarning(void *ctx, const char *msg, ...)

// handles warnings encountered while parsing RNG schema
void rngWarn(void *ctx, const char *msg, ...)
{
    NSLog(@"Relax NG warn: %s", msg);
}

// handles errors encountered while parsing RNG schema
void rngErr(void *ctx, const char *msg, ...)
{
    NSLog(@"Relax NG err: %s", msg);
}

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    char *docfurl = "/Users/itod/Desktop/espnfeeds/xml/golf.xml";
    char *schemafurl = "/Users/itod/Desktop/espnfeeds/rng/golf.rng";

    // RELAX NG Parser Context
    xmlRelaxNGParserCtxtPtr ctxt = xmlRelaxNGNewParserCtxt(schemafurl);
    xmlRelaxNGSetParserErrors(ctxt,
                              (xmlRelaxNGValidityErrorFunc)rngErr,
                              (xmlRelaxNGValidityWarningFunc)rngWarn,
                              NULL);

    xmlRelaxNGPtr schema = xmlRelaxNGParse(ctxt);
    xmlRelaxNGFreeParserCtxt(ctxt);

    xmlTextReaderPtr reader = xmlNewTextReaderFilename(docfurl);

    xmlTextReaderRelaxNGSetSchema(reader, schema);

    xmlTextReaderSetErrorHandler(reader, (xmlTextReaderErrorFunc)readerErr, NULL);
    xmlTextReaderSetStructuredErrorHandler(reader, (xmlStructuredErrorFunc)structErr, NULL);

    while (xmlTextReaderRead(reader));

    NSLog(@"Done.Is Valid?: %i", xmlTextReaderIsValid(reader));

    xmlFreeTextReader(reader);
    xmlRelaxNGFree(schema);

    [pool release];
    return 0;
}


About this entry