com.sensia.gwt.relaxNG.RNGParser.java Source code

Java tutorial

Introduction

Here is the source code for com.sensia.gwt.relaxNG.RNGParser.java

Source

/***************************** BEGIN LICENSE BLOCK ***************************
    
 The contents of this file are Copyright (C) 2011 Sensia Software LLC.
 All Rights Reserved.
     
 Contributor(s): 
Alexandre Robin <alex.robin@sensiasoftware.com>
     
******************************* END LICENSE BLOCK ***************************/

package com.sensia.gwt.relaxNG;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.NamedNodeMap;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.XMLParser;
import com.sensia.relaxNG.RNGAttribute;
import com.sensia.relaxNG.RNGChoice;
import com.sensia.relaxNG.RNGData;
import com.sensia.relaxNG.RNGDefine;
import com.sensia.relaxNG.RNGElement;
import com.sensia.relaxNG.RNGGrammar;
import com.sensia.relaxNG.RNGGroup;
import com.sensia.relaxNG.RNGList;
import com.sensia.relaxNG.RNGOneOrMore;
import com.sensia.relaxNG.RNGOptional;
import com.sensia.relaxNG.RNGRef;
import com.sensia.relaxNG.RNGTag;
import com.sensia.relaxNG.RNGTagList;
import com.sensia.relaxNG.RNGText;
import com.sensia.relaxNG.RNGValue;
import com.sensia.relaxNG.RNGZeroOrMore;
import com.sensia.relaxNG.XSDAnyURI;
import com.sensia.relaxNG.XSDBoolean;
import com.sensia.relaxNG.XSDDateTime;
import com.sensia.relaxNG.XSDDecimal;
import com.sensia.relaxNG.XSDDouble;
import com.sensia.relaxNG.XSDInteger;
import com.sensia.relaxNG.XSDString;
import com.sensia.relaxNG.XSDToken;

/**
 * <p><b>Title:</b>
 * RNGParser
 * </p>
 *
 * <p><b>Description:</b><br/>
 * This class parses a RelaxNG document to the corresponding object hierarchy
 * using GWT DOM so that it can be done in web client.
 * </p>
 *
 * <p>Copyright (c) 2011</p>
 * @author Alexandre Robin
 * @date Aug 27, 2011
 */
public class RNGParser {
    protected final static String RNG_NS_URI = "http://relaxng.org/ns/structure/1.0";
    protected final static String ANNOT_NS_URI = "http://relaxng.org/ns/compatibility/annotations/1.0";
    protected static Map<String, RNGGrammar> grammarCache = new HashMap<String, RNGGrammar>();
    protected RNGParserCallback callback;
    protected RNGGrammar grammar;
    protected int numIncludes;

    public static void clearCache() {
        grammarCache.clear();
    }

    public void parse(final String url, final RNGParserCallback callback) {
        this.callback = callback;

        // if grammar has already been parsed
        grammar = grammarCache.get(url);
        if (grammar != null) {
            callback.onParseDone(grammar);
            return;
        }

        // otherwise load included grammar and parse it asynchronously
        RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL.encode(url));
        try {

            builder.sendRequest(null, new RequestCallback() {
                public void onError(Request request, Throwable exception) {
                    // Couldn't connect to server (could be timeout, SOP violation, etc.)
                }

                public void onResponseReceived(Request request, Response resp) {
                    if (200 == resp.getStatusCode()) {
                        String text = resp.getText();
                        parse(url, text);
                    } else {
                        // Handle the error.  Can get the status text from response.getStatusText()
                    }
                }
            });
        } catch (RequestException e) {
            e.printStackTrace();
        }
    }

    public RNGGrammar parse(String url, String xml) {
        Document dom = XMLParser.parse(xml);
        XMLParser.removeWhitespace(dom);
        parseGrammar(url, dom.getDocumentElement());
        return grammar;
    }

    protected void parseChildren(RNGTag parent, Element parentElt) {
        NodeList children = parentElt.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (!(node instanceof Element))
                continue;
            Element elt = (Element) node;
            String eltName = getLocalName(elt);
            String nsUri = node.getNamespaceURI();

            // annotation
            if (nsUri != null && nsUri.equals(ANNOT_NS_URI) && eltName.equals("documentation"))
                parent.setAnnotation(getTextValue(node));

            // parse nested relaxNG tags
            if (parent instanceof RNGTagList) {
                if (!nsUri.equals(RNG_NS_URI))
                    continue;

                RNGTag tag = null;
                if (eltName.equals("ref")) {
                    RNGRef ref = new RNGRef();
                    ref.setParentGrammar(grammar);
                    ref.setPatternName(elt.getAttribute("name"));
                    parseChildren(ref, elt);
                    tag = ref;
                }

                else if (eltName.equals("element")) {
                    RNGElement rngElt = new RNGElement();
                    QName qname = parseRNGObjectName(elt);
                    rngElt.setName(qname.localName);
                    rngElt.setNamespace(qname.namespaceURI);
                    parseChildren(rngElt, elt);
                    tag = rngElt;
                }

                else if (eltName.equals("attribute")) {
                    RNGAttribute rngAtt = new RNGAttribute();
                    QName qname = parseRNGObjectName(elt);
                    rngAtt.setName(qname.localName);
                    rngAtt.setNamespace(qname.namespaceURI);
                    parseChildren(rngAtt, elt);
                    tag = rngAtt;
                }

                else if (eltName.equals("optional")) {
                    tag = new RNGOptional();
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("choice")) {
                    tag = new RNGChoice();
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("zeroOrMore")) {
                    tag = new RNGZeroOrMore();
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("oneOrMore")) {
                    tag = new RNGOneOrMore();
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("group")) {
                    tag = new RNGGroup();
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("list")) {
                    tag = new RNGList();
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("text")) {
                    tag = new RNGText();
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("data")) {
                    tag = parseDataType(elt);
                    parseChildren(tag, elt);
                }

                else if (eltName.equals("value")) {
                    RNGValue val = new RNGValue();
                    val.setText(getTextValue(elt));
                    parseChildren(val, elt);
                    tag = val;
                } else if (eltName.equals("defaultValue")) {
                    RNGValue val = new RNGValue();
                    val.setText(getTextValue(elt));
                    parseChildren(val, elt);
                    tag = val;
                }

                if (tag != null)
                    ((RNGTagList) parent).add(tag);
            }
        }
    }

    protected RNGGrammar parseGrammar(String url, Element grammarElt) {
        grammar = new RNGGrammar();
        grammar.setId(url);

        // figure out base URL
        int lastSlash = url.lastIndexOf('/');
        String baseUrl = (lastSlash > 0) ? url.substring(0, lastSlash + 1) : "";

        // namespaces
        NamedNodeMap atts = grammarElt.getAttributes();
        for (int i = 0; i < atts.getLength(); i++) {
            Node node = atts.item(i);
            if (node.getPrefix() != null && node.getPrefix().equals("xmlns")) {
                String prefix = node.getNodeName().substring(node.getNodeName().indexOf(':') + 1);
                String uri = node.getNodeValue();

                if (uri.equals(RNGParser.RNG_NS_URI) || uri.equals(RNGParser.ANNOT_NS_URI))
                    continue;

                grammar.addNamespace(prefix, uri);
            }
        }

        // get included grammars
        List<Element> includeElts = new ArrayList<Element>();
        NodeList children = grammarElt.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            if (children.item(i).getNodeType() != Node.ELEMENT_NODE)
                continue;

            Element child = (Element) children.item(i);
            String childName = child.getNodeName();

            if (childName.equals("include"))
                includeElts.add(child);
        }
        numIncludes = includeElts.size();

        // finish parsing now if no includes
        if (numIncludes == 0)
            finishParsing(grammarElt);

        // else parse all included grammar asynchronously
        // we will finish parsing on callback when all includes are loaded
        else
            for (Element includeElt : includeElts)
                parseIncludedGrammar(baseUrl, includeElt);

        return grammar;
    }

    protected void parseIncludedGrammar(String baseUrl, final Element includeElt) {
        String url = includeElt.getAttribute("href");
        final String cleanUrl = canonicalizeUrl(baseUrl, url);

        System.out.println("Parsing included grammar: " + cleanUrl);

        RNGParser parser = new RNGParser();
        parser.parse(cleanUrl, new RNGParserCallback() {
            @Override
            public void onParseDone(RNGGrammar g) {
                System.out.println("Done parsing: " + g.getId());

                // parse embedded patterns (start, defines)
                parsePatternsAndAddToGrammar(g, includeElt);
                grammar.setStartPattern(g.getStartPattern());

                grammar.getIncludedGrammars().put(cleanUrl, g);
                if (grammar.getIncludedGrammars().size() == numIncludes)
                    finishParsing((Element) includeElt.getParentNode());
            }
        });
    }

    protected void finishParsing(Element grammarElt) {
        parsePatternsAndAddToGrammar(grammar, grammarElt);
        grammarCache.put(grammar.getId(), grammar);
        callback.onParseDone(grammar);
    }

    protected void parsePatternsAndAddToGrammar(RNGGrammar grammar, Element parentElt) {
        NodeList children = parentElt.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (!(node instanceof Element))
                continue;

            Element elt = (Element) node;
            String eltName = getLocalName(elt);

            String nsUri = node.getNamespaceURI();
            if (!nsUri.equals(RNG_NS_URI))
                continue;

            if (eltName.equals("define")) {
                RNGDefine pattern = new RNGDefine();
                pattern.setId(elt.getAttribute("name"));
                parseChildren(pattern, elt);
                grammar.getPatterns().put(pattern.getId(), pattern);
            }

            else if (eltName.equals("start")) {
                RNGGroup startPattern = new RNGGroup();
                parseChildren(startPattern, elt);
                grammar.setStartPattern(startPattern);
            }
        }
    }

    protected String canonicalizeUrl(String baseUrl, String url) {
        if (!url.startsWith("http"))
            url = baseUrl + url;

        // remove all . and ..
        String[] path = url.split("/");
        List<String> newPath = new ArrayList<String>();
        for (int i = 0; i < path.length; i++) {
            if (path[i].equals("."))
                continue;

            if (path[i].equals("..") && newPath.size() > 0)
                newPath.remove(newPath.get(newPath.size() - 1));
            else
                newPath.add(path[i]);
        }

        // rebuild URL
        StringBuilder cleanUrl = new StringBuilder();
        for (String part : newPath) {
            cleanUrl.append(part);
            cleanUrl.append('/');
        }
        cleanUrl.deleteCharAt(cleanUrl.length() - 1);

        return cleanUrl.toString();
    }

    protected RNGData<?> parseDataType(Element elt) {
        RNGData<?> data = null;
        String dataType = elt.getAttribute("type");

        // instantiate the right class
        if (dataType.equals("boolean"))
            data = new XSDBoolean();
        else if (dataType.equals("dateTime"))
            data = new XSDDateTime();
        else if (dataType.equals("decimal"))
            data = new XSDDecimal();
        else if (dataType.equals("double"))
            data = new XSDDouble();
        else if (dataType.equals("integer"))
            data = new XSDInteger();
        else if (dataType.equals("string"))
            data = new XSDString();
        else if (dataType.equals("anyURI"))
            data = new XSDAnyURI();
        else if (dataType.equals("token"))
            data = new XSDToken();
        else
            data = new RNGData<String>();

        // read params
        NodeList paramElts = elt.getElementsByTagName("param");
        for (int i = 0; i < paramElts.getLength(); i++) {
            Element paramElt = (Element) paramElts.item(i);
            String name = paramElt.getAttribute("name");
            String value = getTextValue(paramElt);
            data.getParams().put(name, value);
        }

        data.setType(dataType);
        return data;
    }

    protected QName parseRNGObjectName(Element elt) {
        String qname = elt.getAttribute("name");
        if (qname != null) {
            return parseName(qname);
        } else {
            NodeList children = elt.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node node = children.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE && getLocalName(node).equals("name")) {
                    String nsUri = ((Element) node).getAttribute("ns");
                    String localName = getTextValue(elt);
                    return new QName(nsUri, localName);
                }
            }
        }

        return null;
    }

    protected QName parseName(String qname) {
        String[] tokens = qname.split(":");
        String localName = null, nsUri = null;

        if (tokens.length == 1) {
            nsUri = null;
            localName = tokens[0];
        } else {
            nsUri = grammar.getNsPrefixToUri().get(tokens[0]);
            localName = tokens[1];
        }

        return new QName(nsUri, localName);
    }

    protected String getLocalName(Node node) {
        QName qname = parseName(node.getNodeName());
        return qname.localName;
    }

    protected String getTextValue(Node node) {
        if (!node.hasChildNodes() || node.getFirstChild().getNodeType() != Node.TEXT_NODE)
            return null;

        String textValue = node.getFirstChild().getNodeValue();

        if (textValue != null)
            return textValue.replace("\\s+", " ");
        else
            return null;
    }
}