com.hp.hpl.inkml.InkMLDOMParser.java Source code

Java tutorial

Introduction

Here is the source code for com.hp.hpl.inkml.InkMLDOMParser.java

Source

/*****************************************************************************************
 * Copyright (c) 2008 Hewlett-Packard Development Company, L.P. Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *****************************************************************************************/
/****************************************************************************************
 * SVN MACROS $Revision: 274 $ $Author: selvarmu $ $LastChangedDate: 2008-07-07 21:24:00 +0530 (Mon, 07 Jul 2008) $
 ************************************************************************************/
package com.hp.hpl.inkml;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Logger;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * This class provides DOM implementation of the InkMLParser interface. It uses the Xerces parser bundled with JDK 1.5 or higher versions.
 * 
 * @author Muthuselvam Selvaraj
 * @version 0.5.0
 */
public final class InkMLDOMParser implements ErrorHandler {
    private Document inkmlDOMDocument;
    private final InkMLProcessor inkMLProcessor;

    private static final Logger LOG = Logger.getLogger(InkMLDOMParser.class.getName());

    /**
     * Constructor
     * 
     * @param processor
     */
    public InkMLDOMParser(final InkMLProcessor processor) {
        super();
        this.inkMLProcessor = processor;
    }

    /**
     * bind data from parsed xml to the inkml data objects it passes the element to coresponding getXXX method based on their type(tag name) and performs the
     * data binding
     * 
     * @param ink the ink document data object to be populated with parsing an inkml file
     */
    protected void bindData(final Ink ink) throws InkMLException {
        InkMLDOMParser.LOG.info("\nTo bind the parsed data to InkML data objects.\n");
        if (null == this.inkmlDOMDocument) {
            InkMLDOMParser.LOG.warning("No parsed data available for data binding.");
            return;
        }
        final Element rootElmnt = this.inkmlDOMDocument.getDocumentElement();
        if ("ink".equalsIgnoreCase(rootElmnt.getLocalName())) {
            ink.setDocID(rootElmnt.getAttribute("documentID"));
            final NodeList list = rootElmnt.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                final Node node = list.item(i);
                if (!(node instanceof Element)) {
                    continue;
                }
                final Element element = (Element) node;
                this.addToInkElementList(element, ink);
            }
        } else if ("inkMLFragment".equalsIgnoreCase(rootElmnt.getLocalName())) {
            final NodeList list = rootElmnt.getChildNodes();
            for (int i = 0; i < list.getLength(); i++) {
                final Node node = list.item(i);
                if (!(node instanceof Element)) {
                    continue;
                }
                final Element element = (Element) node;
                this.addToInkElementList(element, ink);
            }
        }
    }

    /**
     * Method to bind the direct children of Ink data object
     * 
     * @param element child element of Ink
     * @param ink ink data object
     * @throws InkMLException
     */
    protected void addToInkElementList(final Element element, final Ink ink) throws InkMLException {

        final String tagName = element.getLocalName();

        // Content of <ink> is ( definitions | context | trace | traceGroup |
        // traceView | annotation | annotationXML )
        if (tagName.equals("definitions")) {
            final Definitions definitions = ink.getDefinitions();
            final NodeList childElements = element.getChildNodes();
            this.addToDefinitions(childElements, definitions);
        } else if (tagName.equals("context")) {
            // a <context> as direct child to <ink> updates the curentContext.
            final Definitions defs = ink.getDefinitions();
            final Context context = this.getContext(element, defs);

            // replace 'implicit contextRef' to the 'id' of the current context
            if ("".equals(context.getContextRef())) {
                context.setContextRef("#" + ink.getCurrentContext().getId());
            }

            context.deriveContextualChildrenData(ink.getDefinitions(), ink.getCurrentContext());

            final List<Ink.ContextChangeStatus> ctxChanges = ink.getContextChanges(context);
            if (ctxChanges.isEmpty()) {
                // no context change occured. Ignore this context element
                return;
            }

            // assign ID and place it in definitions
            if ("".equals(context.getId())) {
                String ctxId = InkMLIDGenerator.getNextIDForContext();
                while (defs.contains(ctxId)) {
                    ctxId = InkMLIDGenerator.getNextIDForContext();
                }
                context.setId(ctxId);
            }
            defs.addToDirectChildrenMap(context);
            this.inkMLProcessor.notifyContextChanged(context, ctxChanges);
            ink.setCurrentContext(context);
        } else if (tagName.equals("trace")) {
            final Trace trace = this.getTrace(element);
            ink.addTrace(trace);
            this.inkMLProcessor.notifyTraceReceived(trace);
            if (!"".equals(trace.getId())) {
                ink.getDefinitions().addToIndirectChildrenMap(trace);
            }
        } else if (tagName.equals("traceGroup")) {
            final TraceGroup traceGroup = this.getTraceGroup(element);
            traceGroup.resolveAssociatedContext(ink.getCurrentContext());
            ink.addTraceGroup(traceGroup);
            this.inkMLProcessor.notifyTraceGroupReceived(traceGroup);

            // store traceData having 'id' attibute to IndirectChildren
            if (!"".equals(traceGroup.getId())) {
                ink.getDefinitions().addToIndirectChildrenMap(traceGroup);
            }
            final List<TraceDataElement> children = traceGroup.getTraceDataList();
            if (null != children) {
                for (int i = 0; i < children.size(); i++) {
                    final TraceDataElement child = children.get(i);
                    if (!"".equals(child.getId())) {
                        ink.getDefinitions().addToIndirectChildrenMap(child);
                    }
                }
            }
        } else if (tagName.equals("traceView")) {
            final TraceView traceView = this.getTraceView(element, ink.getDefinitions());
            traceView.setAssociatedContext(ink.getCurrentContext());
            ink.addTraceView(traceView);
            this.inkMLProcessor.notifyTraceViewReceived(traceView);
            if (!"".equals(traceView.getId())) {
                ink.getDefinitions().addToDirectChildrenMap(traceView);
            }
        } else if (tagName.equals("annotation")) {
            final Annotation annotation = this.getAnnotation(element);
            ink.addAnnotation(annotation);
        } else if (tagName.equals("annotationXML")) {
            final AnnotationXML aXml = this.getAnnotationXML(element);
            ink.addAnnotationXML(aXml);
        } else {
            throw new InkMLException(
                    "The Element " + tagName + " is not a valid Child Element for InkML <Ink> Element");
        }
    }

    /**
     * Method to bind AnnotationXML element
     * 
     * @param element the AnnotationXML element
     * @return AnnotationXML data object
     * @throws InkMLException
     */
    protected AnnotationXML getAnnotationXML(final Element element) throws InkMLException {
        final AnnotationXML aXml = new AnnotationXML();
        final NamedNodeMap attributesMap = element.getAttributes();
        final int length = attributesMap.getLength();
        for (int index = 0; index < length; index++) {
            final Attr attribute = (Attr) attributesMap.item(index);
            final String attributeName = attribute.getName();
            if ("type".equals(attributeName)) {
                aXml.setType(attribute.getValue());
            } else if ("encoding".equals(attributeName)) {
                aXml.setEncoding(attribute.getValue());
            } else {
                aXml.addToOtherAttributesMap(attributeName, attribute.getValue());
            }
        }
        InkMLDOMParser.LOG.finest("annotationXML received: " + element.toString());
        final NodeList list = element.getChildNodes();
        final int nChildren = list.getLength();
        if (nChildren > 0) {
            for (int i = 0; i < nChildren; i++) {
                final Node node = list.item(i);
                if (!(node instanceof Element)) {
                    continue;
                }
                final Element childElement = (Element) node;
                // get the tagName to use as Key in the valueMap
                final String tagName = childElement.getLocalName();
                // String key = this.parentXPath+"/"+tagName;
                final String value = childElement.getFirstChild().getNodeValue();
                // propertyElementsMap.put(key, childElement);
                // propertyElementsMap.put(key, value);
                aXml.addToPropertyElementsMap(tagName, value);
                InkMLDOMParser.LOG
                        .finer("The property with name = " + tagName + " is added to the propertyElementsMap.");
            }
        }
        return aXml;
    }

    /**
     * Method to bind Annotation element
     * 
     * @param element the Annotation element
     * @return Annotation data object
     * @throws InkMLException
     */
    protected Annotation getAnnotation(final Element element) throws InkMLException {
        final Annotation annotation = new Annotation();

        final NamedNodeMap attributesMap = element.getAttributes();
        final int length = attributesMap.getLength();
        for (int index = 0; index < length; index++) {
            final Attr attribute = (Attr) attributesMap.item(index);
            final String attributeName = attribute.getName();
            if ("type".equals(attributeName)) {
                annotation.setType(attribute.getValue());
            } else if ("encoding".equals(attributeName)) {
                annotation.setEncoding(attribute.getValue());
            } else {
                annotation.addToOtherAttributesMap(attributeName, attribute.getValue());
            }
        }
        final Node valueNode = element.getFirstChild();
        if (null != valueNode) {
            annotation.setAnnotationTextValue(valueNode.getNodeValue());
        }
        return annotation;
    }

    /**
     * Method to bind TraceView element
     * 
     * @param element the TraceView element
     * @param definitions the definitions data object to resolve the reference attributes
     * @return TraceView data object
     * @throws InkMLException
     */
    protected TraceView getTraceView(final Element element, final Definitions definitions) throws InkMLException {
        final TraceView traceView = new TraceView();
        final String id = element.getAttribute("id");
        if (!"".equals(id)) {
            traceView.setId(id);
        }
        final String contextRef = element.getAttribute("contextRef");
        if (!"".equals(contextRef)) {
            traceView.setContextRef(contextRef);
        }
        traceView.resolveContext(definitions);
        final String traceDataRef = element.getAttribute("traceDataRef");
        if (!"".equals(traceDataRef)) {
            traceView.setTraceDataRef(traceDataRef);
            traceView.setFromAttribute(element.getAttribute("from"));
            traceView.setToAttribute(element.getAttribute("to"));
            traceView.setSelectedTree(definitions);
        } else {
            // <traceView> does not have 'traceDataRef' attribute; then it is used to -
            // group child <traceView> elements.
            final NodeList list = element.getElementsByTagName("traceView");
            final int nData = list.getLength();
            if (0 != nData) {
                traceView.initChildViewList();
                for (int i = 0; i < nData; i++) {
                    final Element traceViewElement = (Element) list.item(i);
                    final TraceView childTV = this.getTraceView(traceViewElement, definitions);
                    traceView.addToChildTraceViewList(childTV);
                }
                // add child element of type annotation and annotationXML
                traceView.processChildren(definitions);
            }
        }
        return traceView;
    }

    /**
     * Method to bind TraceGroup element
     * 
     * @param element the TraceGroup element
     * @return TraceGroup data object
     * @throws InkMLException
     */
    protected TraceGroup getTraceGroup(final Element element) throws InkMLException {
        final TraceGroup traceGroup = new TraceGroup();

        // bind attribute
        final String id = element.getAttribute("id");
        final String contextref = element.getAttribute("contextRef");
        final String brushRef = element.getAttribute("brushRef");
        if (!"".equals(id)) {
            traceGroup.setId(id);
        }
        if (!"".equals(contextref)) {
            traceGroup.setContextRef(contextref);
        }
        if (!"".equals(brushRef)) {
            traceGroup.setBrushRef(brushRef);
        }

        // bind child element
        final NodeList list = element.getChildNodes();
        for (int i = 0; i < list.getLength(); i++) {
            final Node node = list.item(i);
            if (!(node instanceof Element)) {
                continue;
            }
            final Element child = (Element) node;
            final String tagName = child.getLocalName();
            if ("trace".equals(tagName)) {
                final Trace trace = this.getTrace(child);
                trace.setParentTraceGroup(traceGroup);
                traceGroup.addToTraceData(trace);
            } else if ("traceGroup".equals(tagName)) {
                final TraceGroup childTG = this.getTraceGroup(child);
                childTG.setParentTraceGroup(traceGroup);
                traceGroup.addToTraceData(childTG);
            }
        }
        return traceGroup;
    }

    /**
     * Method to bind Trace element
     * 
     * @param element the Trace element
     * @return Trace data object
     * @throws InkMLException
     */
    protected Trace getTrace(final Element element) throws InkMLException {
        final Trace trace = new Trace();
        // set value of the object from the value of the DOM element
        // Extract and set Attribute values
        final NamedNodeMap attrMap = element.getAttributes();
        final int length = attrMap.getLength();
        for (int i = 0; i < length; i++) {
            final Node attr = attrMap.item(i);
            trace.setAttribute(attr.getLocalName(), attr.getNodeValue());
        }
        // get trace data
        String traceText = "";
        final NodeList nl = element.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            if (nl.item(i).getNodeType() == Node.TEXT_NODE) {
                traceText += nl.item(i).getNodeValue();
            }
        }
        final Ink ink = this.inkMLProcessor.getInk();
        final Context currCtx = ink.getCurrentContext();
        final Definitions defs = ink.getDefinitions();
        trace.resolveAssociatedContext(currCtx, defs);
        trace.processTraceElement(traceText, currCtx, defs);

        return trace;
    }

    /**
     * Method to bind the children of Definitions element
     * 
     * @param elements the Definitions child element
     * @param definitions the definitions data object to resolve the reference attributes
     * @throws InkMLException
     */
    protected void addToDefinitions(final NodeList elements, final Definitions definitions) throws InkMLException {
        for (int i = 0; i < elements.getLength(); i++) {
            final Node node = elements.item(i);
            if (!(node instanceof Element)) {
                continue;
            }
            final Element element = (Element) node;
            final String tagName = element.getLocalName();
            final String id = element.getAttribute("id");
            if ("".equals(id)) {
                throw new InkMLException("Elements within a <definitions> block must have an 'id' attribute. A "
                        + tagName + " element do not have 'id' attribute.");
            }
            InkElement inkElement = null;
            // when the object of child elements created, the elements having a non empty id are added to definitions.
            // This is done in the constructors of all the child elements of Definitions
            if ("brush".equalsIgnoreCase(tagName)) {
                inkElement = this.getBrush(element);
            } else if ("traceFormat".equalsIgnoreCase(tagName)) {
                inkElement = this.getTraceFormat(element, definitions);
            } else if ("context".equalsIgnoreCase(tagName)) {
                final Context context = this.getContext(element, definitions);
                // resolve implicit references for contextual elements to their counterpart in the "DefaultContext"
                context.resolveImplicitReferenceWithDefaultContext();
                inkElement = context;
            } else if ("inkSource".equalsIgnoreCase(tagName)) {
                inkElement = this.getInkSource(element, definitions);
            } else if ("trace".equalsIgnoreCase(tagName)) {
                inkElement = this.getTrace(element);
            } else if ("traceGroup".equalsIgnoreCase(tagName)) {
                inkElement = this.getTraceGroup(element);
            } else if ("traceView".equalsIgnoreCase(tagName)) {
                inkElement = this.getTraceView(element, definitions);
            } else if ("canvas".equalsIgnoreCase(tagName)) {
                inkElement = this.getCanvas(element, definitions);
            } else if ("canvasTransform".equalsIgnoreCase(tagName)) {
                inkElement = this.getCanvasTransform(element);
            } else if ("timestamp".equalsIgnoreCase(tagName)) {
                inkElement = this.getTimestamp(element);
            } else if ("mapping".equalsIgnoreCase(tagName)) {
                inkElement = this.getMapping(element);
            } else {
                throw new InkMLException("Parse Error: The element " + tagName
                        + "should not be a child to <definitions> element.\n");
            }
            definitions.addToDirectChildrenMap(inkElement);
        }
    }

    /**
     * Method to bind Mapping element
     * 
     * @param element the Mapping element
     * @return Mapping data object
     * @throws InkMLException
     */
    protected Mapping getMapping(final Element element) throws InkMLException {
        final Mapping mapping = new Mapping();
        // Extract and set Attribute values
        final String type = element.getAttribute("type");
        InkMLDOMParser.LOG.info("mapping type=" + type);
        if (!"identity".equalsIgnoreCase(type) && !"unknown".equalsIgnoreCase(type)) {
            throw new InkMLException(
                    "Feature not implemented: 'Mapping' with 'type' attribute other than identity and unknown");
        }
        mapping.setType(type);
        final String id = element.getAttribute("id");
        if (!id.equals("")) {
            mapping.setId(id);
        }
        return mapping;
    }

    /**
     * Method to bind Timestamp element
     * 
     * @param element the Timestamp element
     * @return Timestamp data object
     * @throws InkMLException
     */
    protected Timestamp getTimestamp(final Element element) throws InkMLException {
        throw new InkMLException("Feature not implemented. Mapping of type other than identity or unknown");
    }

    /**
     * Method to bind CanvasTransform element
     * 
     * @param element the CanvasTransform element
     * @return CanvasTransform data object
     * @throws InkMLException
     */
    protected CanvasTransform getCanvasTransform(final Element element) throws InkMLException {
        final CanvasTransform canvasTransform = new CanvasTransform();
        // Extract and set Attribute values
        final NamedNodeMap attrMap = element.getAttributes();
        final int length = attrMap.getLength();
        for (int i = 0; i < length; i++) {
            final Node attr = attrMap.item(i);
            canvasTransform.setAttribute(attr.getLocalName(), attr.getNodeValue());
        }

        final NodeList list = element.getElementsByTagName("mapping");
        final int nMappingChildren = list.getLength();
        if (nMappingChildren == 2) {
            canvasTransform.setForwardMapping(this.getMapping((Element) list.item(0)));
            canvasTransform.setReverseMapping(this.getMapping((Element) list.item(1)));
        } else if (nMappingChildren == 1) {
            canvasTransform.setForwardMapping(this.getMapping((Element) list.item(0)));
        }
        return canvasTransform;
    }

    /**
     * Method to bind Canvas element
     * 
     * @param element the Canvas element
     * @param definitions the definitions data object to resolve the reference attributes
     * @return Canvas data object
     * @throws InkMLException
     */
    protected Canvas getCanvas(final Element element, final Definitions definitions) throws InkMLException {
        final Canvas canvas = new Canvas();
        // Extract and set Attribute values
        final NamedNodeMap attrMap = element.getAttributes();
        final int length = attrMap.getLength();
        for (int i = 0; i < length; i++) {
            final Node attr = attrMap.item(i);
            canvas.setAttribute(attr.getLocalName(), attr.getNodeValue());
        }

        final NodeList list = element.getElementsByTagName("traceFormat");
        if (list.getLength() != 0) {
            canvas.setTraceFormat(this.getTraceFormat((Element) list.item(0), definitions));
        }

        return canvas;
    }

    /**
     * Method to bind InkSource element
     * 
     * @param element the InkSource element
     * @param definitions the definitions data object to resolve the reference attributes
     * @return InkSource data object
     * @throws InkMLException
     */
    protected InkSource getInkSource(final Element element, final Definitions definitions) throws InkMLException {
        final InkSource inkSrc = new InkSource();
        // Extract and set Attribute values
        final NamedNodeMap attrMap = element.getAttributes();
        final int length = attrMap.getLength();
        for (int i = 0; i < length; i++) {
            final Node attr = attrMap.item(i);
            inkSrc.setAttribute(attr.getLocalName(), attr.getNodeValue());
        }

        NodeList list = element.getElementsByTagName("traceFormat");
        if (list.getLength() != 0) {
            inkSrc.setTraceFormat(this.getTraceFormat((Element) list.item(0), definitions));
        }

        list = element.getElementsByTagName("sampleRate");
        if (list.getLength() != 0) {
            inkSrc.setSampleRate(this.getSampleRate((Element) list.item(0), inkSrc));
        }
        list = element.getElementsByTagName("latency");
        if (list.getLength() != 0) {
            inkSrc.setLatency(this.getLatency((Element) list.item(0), inkSrc));
        }
        list = element.getElementsByTagName("activeArea");
        if (list.getLength() != 0) {
            inkSrc.setActiveArea(this.getActiveArea((Element) list.item(0), inkSrc));
        }
        list = element.getElementsByTagName("srcProperty");
        for (int i = 0; i < list.getLength(); i++) {
            inkSrc.addSourceProperty(this.getSourceProperty((Element) list.item(i), inkSrc));
        }
        list = element.getElementsByTagName("channelProperties");
        if (list.getLength() != 0) {
            inkSrc.setChannelProperties(this.getChannelProperties((Element) list.item(0), inkSrc));
        }
        return inkSrc;
    }

    /**
     * Method to bind InkSource.Latency element
     * 
     * @param element the InkSource.Latency element
     * @param inkSrc the enclosing InkSource data object
     * @return InkSource.Latency data object
     * @throws InkMLException
     */
    protected InkSource.Latency getLatency(final Element element, final InkSource inkSrc) throws InkMLException {
        final String value = element.getAttribute("value");
        if ("".equals(value)) {
            return inkSrc.new Latency(Double.valueOf(value).doubleValue());
        }
        return null;
    }

    /**
     * Method to bind InkSource.ActiveArea element
     * 
     * @param element the InkSource.ActiveArea element
     * @param inkSrc the enclosing InkSource data object
     * @return InkSource.ActiveArea data object
     * @throws InkMLException
     */
    protected InkSource.ActiveArea getActiveArea(final Element element, final InkSource inkSrc)
            throws InkMLException {
        final InkSource.ActiveArea activeArea = inkSrc.new ActiveArea();
        final NamedNodeMap attrMap = element.getAttributes();
        final int length = attrMap.getLength();
        for (int i = 0; i < length; i++) {
            final Node attr = attrMap.item(i);
            final String attrName = attr.getLocalName();
            if ("size".equals(attrName)) {
                activeArea.setSize(attr.getNodeValue());
            }
            if ("height".equals(attrName)) {
                activeArea.setHegiht(Double.valueOf(attr.getNodeValue()).doubleValue());
            }
            if ("width".equals(attrName)) {
                activeArea.setWidth(Double.valueOf(attr.getNodeValue()).doubleValue());
            }
            if ("units".equals(attrName)) {
                activeArea.setUnits(attr.getNodeValue());
            }
        }
        return activeArea;
    }

    /**
     * Method to bind InkSource.SampleRate element
     * 
     * @param element the InkSource.SampleRate element
     * @param inkSrc the enclosing InkSource data object
     * @return InkSource.SampleRate data object
     * @throws InkMLException
     */
    protected InkSource.SampleRate getSampleRate(final Element element, final InkSource inkSrc)
            throws InkMLException {
        final String isUniform = element.getAttribute("uniform");
        final String value = element.getAttribute("value");
        InkSource.SampleRate sampleRate;
        if ("".equals(isUniform)) {
            sampleRate = inkSrc.new SampleRate(Double.valueOf(value).doubleValue());
        } else {
            sampleRate = inkSrc.new SampleRate(Double.valueOf(value).doubleValue(),
                    Boolean.valueOf(isUniform).booleanValue());
        }
        return sampleRate;
    }

    /**
     * Method to bind InkSource.ChannelProperties element
     * 
     * @param element the InkSource.ChannelProperties element
     * @param inkSrc the enclosing InkSource data object
     * @return InkSource.ChannelProperties data object
     * @throws InkMLException
     */
    protected InkSource.ChannelProperties getChannelProperties(final Element element, final InkSource inkSrc)
            throws InkMLException {
        final NodeList list = element.getElementsByTagName("channelProperty");
        final int nChnProperty = list.getLength();
        if (0 != nChnProperty) {
            final InkSource.ChannelProperties chnProps = inkSrc.new ChannelProperties();
            InkSource.ChannelProperties.ChannelProperty chnProp;
            for (int i = 0; i < nChnProperty; i++) {
                chnProp = this.getChannelProperty((Element) list.item(i), chnProps);
                chnProps.addChannelProperty(chnProp);
            }
            return chnProps;
        }
        return null;
    }

    /**
     * Method to bind InkSource.ChannelProperties.ChannelProperty element
     * 
     * @param element the InkSource.ChannelProperties.ChannelProperty element
     * @param chnProps the enclosing InkSource.ChannelProperties data object
     * @return InkSource.ChannelProperties.ChannelProperty data object
     * @throws InkMLException
     */
    protected InkSource.ChannelProperties.ChannelProperty getChannelProperty(final Element element,
            final InkSource.ChannelProperties chnProps) throws InkMLException {
        InkSource.ChannelProperties.ChannelProperty chnProp = null;
        final String channel = element.getAttribute("channel");
        final String name = element.getAttribute("name");
        final String value = element.getAttribute("value");
        final String units = element.getAttribute("units");
        if ("".equals(units)) {
            chnProp = chnProps.new ChannelProperty(channel, name, Double.valueOf(value).doubleValue());
        } else {
            chnProp = chnProps.new ChannelProperty(channel, name, Double.valueOf(value).doubleValue(), units);
        }
        return chnProp;
    }

    /**
     * Method to bind InkSource.SourceProperty element
     * 
     * @param element the InkSource.SourceProperty element
     * @param inkSrc the enclosing InkSource data object
     * @return InkSource.SourceProperty data object
     * @throws InkMLException
     */
    protected InkSource.SourceProperty getSourceProperty(final Element element, final InkSource inkSrc)
            throws InkMLException {
        final String name = element.getAttribute("name");
        final String value = element.getAttribute("value");
        final String units = element.getAttribute("units");
        InkSource.SourceProperty srcProperty;
        if ("".equals(units)) {
            srcProperty = inkSrc.new SourceProperty(name, Double.valueOf(value).doubleValue());
        } else {
            srcProperty = inkSrc.new SourceProperty(name, Double.valueOf(value).doubleValue(), units);
        }
        return srcProperty;
    }

    /**
     * Method to bind Context element
     * 
     * @param element the Context element
     * @param definitions the definitions data object to resolve the reference attributes
     * @return Context data object
     * @throws InkMLException
     */
    protected Context getContext(final Element element, final Definitions definitions) throws InkMLException {
        final Context context = new Context();
        // set value of the object from the value of the DOM element
        // Extract and set Attribute values
        final NamedNodeMap attrMap = element.getAttributes();
        final int length = attrMap.getLength();
        for (int i = 0; i < length; i++) {
            final Node attr = attrMap.item(i);
            context.setAttribute(attr.getLocalName(), attr.getNodeValue());
        }
        final NodeList list = element.getChildNodes();
        for (int i = 0; i < list.getLength(); i++) {
            final Node node = list.item(i);
            if (!(node instanceof Element)) {
                continue;
            }
            final Element child = (Element) node;
            this.addToContextChildrenList(child, context, definitions);
        }
        return context;
    }

    /**
     * Method to bind the children of context element
     * 
     * @param ctxChild child element of context element
     * @param context context data object
     * @param definitions the definitions data object to resolve the reference attributes
     * @throws InkMLException
     */
    protected void addToContextChildrenList(final Element ctxChild, final Context context,
            final Definitions definitions) throws InkMLException {
        final String tagName = ctxChild.getLocalName();

        // Content of <context> is ( brush | traceFormat | )
        if (tagName.equals("brush")) {
            final Brush brush = this.getBrush(ctxChild);
            context.addToContextElementList(brush);
        } else if (tagName.equals("traceFormat")) {
            final TraceFormat tf = this.getTraceFormat(ctxChild, definitions);
            context.addToContextElementList(tf);
        } else if (tagName.equals("canvas")) {
            final Canvas canvas = this.getCanvas(ctxChild, definitions);
            context.addToContextElementList(canvas);
        } else if (tagName.equals("canvasTransform")) {
            final CanvasTransform canvasTransform = this.getCanvasTransform(ctxChild);
            context.addToContextElementList(canvasTransform);
        } else if (tagName.equals("inkSource")) {
            final InkSource inkSrc = this.getInkSource(ctxChild, definitions);
            context.addToContextElementList(inkSrc);
        } else if (tagName.equals("timeStamp")) {
            final Timestamp timeStamp = this.getTimestamp(ctxChild);
            context.addToContextElementList(timeStamp);
        }
    }

    /**
     * Method to bind brush element
     * 
     * @param element brush element
     * @return brush data object
     * @throws InkMLException
     */
    protected Brush getBrush(final Element element) throws InkMLException {
        final String id = element.getAttribute("id");
        final Brush brush = new Brush(id);
        final String brushRef = element.getAttribute("brushRef");
        if (!"".equals(brushRef)) {
            brush.setBrushRef(brushRef);
        }

        // Process the child <annotation>, <annotationXML> elements
        final NodeList list = element.getChildNodes();
        for (int i = 0; i < list.getLength(); i++) {
            final Node node = list.item(i);
            if (!(node instanceof Element)) {
                continue;
            }
            final Element childElement = (Element) node;
            final String tagName = childElement.getLocalName();
            if ("annotationXML".equalsIgnoreCase(tagName)) {
                final AnnotationXML aXml = this.getAnnotationXML(childElement);
                brush.setAnnotationXML(aXml);
            } else if ("annotation".equalsIgnoreCase(tagName)) {
                final Annotation annotation = this.getAnnotation(childElement);
                brush.setAnnotation(annotation);
            }
        }
        return brush;
    }

    /**
     * Method to bind TraceFormat
     * 
     * @param element TraceFormat element
     * @param definitions the definitions data object to resolve the reference attributes
     * @return TraceFormat data object
     * @throws InkMLException
     */
    protected TraceFormat getTraceFormat(final Element element, final Definitions definitions)
            throws InkMLException {
        final TraceFormat traceFormat = new TraceFormat();
        final String id = element.getAttribute("id");
        if (!"".equals(id)) {
            traceFormat.setId(element.getAttribute("id"));
            definitions.addToIndirectChildrenMap(traceFormat);
        }
        final String href = element.getAttribute("href");
        if (!"".equals(href)) {
            final TraceFormat refferedTF = definitions.getTraceFormatRefElement(href);
            traceFormat.setHref(href);
            traceFormat.setChannelList(refferedTF.getChannelList());
        }
        final NodeList list = element.getElementsByTagName("channel");
        final int nChannel = list.getLength();

        /*
         * if(0 == nChannel) { // if no channel available in the traceFormat, copy the default channels if(traceFormat.channelMap.size()==0){ // add the channel
         * for default TraceFormat Channel xChannel = new Channel("X",Channel.ChannelType.DECIMAL); Channel yChannel = new
         * Channel("Y",Channel.ChannelType.DECIMAL); traceFormat.addChannel(xChannel); traceFormat.addChannel(yChannel); } }
         */
        for (int i = 0; i < nChannel; i++) {
            final Element channelElmnt = (Element) list.item(i);
            final Channel ch = this.getChannel(channelElmnt);
            traceFormat.addChannel(ch);
        }
        return traceFormat;
    }

    /**
     * Method to bind Channel element
     * 
     * @param channelElement Channel element
     * @return Channel data object
     * @throws InkMLException
     */
    protected Channel getChannel(final Element channelElement) throws InkMLException {
        Channel channel = null;
        final String chnName = channelElement.getAttribute("name");
        if ("".equals(chnName)) {
            throw new InkMLException("Channel element must have value for 'name' attribute");
        } else {
            channel = new Channel(chnName);
        }
        // checking for intermittend channel
        if ("intermittentChannels".equalsIgnoreCase(channelElement.getParentNode().getLocalName())) {
            channel.setIntermittent(true);
        }

        // Extract and set Attribute values
        final NamedNodeMap attrMap = channelElement.getAttributes();
        final int length = attrMap.getLength();
        for (int i = 0; i < length; i++) {
            final Node attr = attrMap.item(i);
            channel.setAttribute(attr.getLocalName(), attr.getNodeValue());
        }
        return channel;
    }

    /** Factory pour les schemas */
    private final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); // NOPMD - not transient

    /**
     * Method to parse the InkML file identified by the inkmlFilename given in the parameter and creates data objects. It performs validation with schema. It
     * must have "ink" as root element with InkML name space specified with xmlns="http://www.w3.org/2003/InkML". The schema location may be specified. If it is
     * not specified or if relative path of the InkML.xsd file is specified, then the InkML.xsd file path must be added to * CLASSPATH for the parser to locate
     * it. An example of a typical header is, <ink xmlns="http://www.w3.org/2003/InkML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     * xsi:schemaLocation="http://www.w3.org/2003/InkML C:\project\schema\inkml.xsd">
     * 
     * @param inkmlFileName
     * @throws InkMLException
     */
    public void parseInkMLFile(final String inkmlFileName) throws InkMLException {

        // Get the DOM document object by parsing the InkML input file
        FileInputStream inputStream = null; // NOPMD - init
        try {
            inputStream = new FileInputStream(inkmlFileName);
            this.parseInkMLFile(inputStream);
        } catch (final IOException e) {
            throw new InkMLException("I/O error while parsing Input InkML XML file.\n Message: " + e.getMessage(),
                    e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    /**
     * Method to parse the InkML file identified by the inkmlFilename given in the parameter and creates data objects. It performs validation with schema. It
     * must have "ink" as root element with InkML name space specified with xmlns="http://www.w3.org/2003/InkML". The schema location may be specified. If it is
     * not specified or if relative path of the InkML.xsd file is specified, then the InkML.xsd file path must be added to * CLASSPATH for the parser to locate
     * it. An example of a typical header is, <ink xmlns="http://www.w3.org/2003/InkML" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     * xsi:schemaLocation="http://www.w3.org/2003/InkML C:\project\schema\inkml.xsd">
     * 
     * @param inkmlFileName
     * @throws InkMLException
     */
    public void parseInkMLFile(final InputStream inputStream) throws InkMLException {

        // Get the DOM document object by parsing the InkML input file
        try {
            final InputSource input = new InputSource(inputStream);

            // get the DOM document object of the input InkML XML file.
            final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            dbFactory.setIgnoringComments(true);
            dbFactory.setNamespaceAware(true);
            dbFactory.setIgnoringElementContentWhitespace(true);
            dbFactory.setValidating(false);
            if (this.isSchemaValidationEnabled()) {
                InkMLDOMParser.LOG.info("Validation using schema is enabled.");
                dbFactory.setSchema(this.schemaFactory
                        .newSchema(new StreamSource(this.getClass().getResourceAsStream("/schema/inkml.xsd"))));
            } else {
                InkMLDOMParser.LOG.info("Validation using schema is disabled.");
            }
            final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            dBuilder.setErrorHandler(this);
            this.inkmlDOMDocument = dBuilder.parse(input);
            InkMLDOMParser.LOG.info("\nInput InkML XML file parsing is completed.\n");
            this.inkMLProcessor.beginInkSession();
            this.bindData(this.inkMLProcessor.getInk());
        } catch (final ParserConfigurationException e) {
            throw new InkMLException("Error in parsing Input InkML XML file.\n Message: " + e.getMessage(), e);
        } catch (final SAXException e) {
            throw new InkMLException("Error in parsing Input InkML XML file.\n Message: " + e.getMessage(), e);
        } catch (final IOException e) {
            throw new InkMLException("I/O error while parsing Input InkML XML file.\n Message: " + e.getMessage(),
                    e);
        }
    }

    /**
     * @return
     */
    private boolean isSchemaValidationEnabled() {
        // check the JVM parameter java -DschemaValidation={"true"|"false"}
        boolean isSchemaValidationEnabled = true;
        final String validationStatus = StringUtils
                .trimToEmpty(System.getProperty("com.hp.hpl.inkml.SchemaValidation"));
        if (StringUtils.isNotEmpty(validationStatus)) {
            if ("true".equalsIgnoreCase(validationStatus)) {
                isSchemaValidationEnabled = true;
            } else {
                isSchemaValidationEnabled = false;
            }
        }
        return isSchemaValidationEnabled;
    }

    /**
     * Method to parse the InkML string markup data identified by the inkmlStr given
     * 
     * @param inkmlStr String markup data
     * @throws InkMLException
     */
    public void parseInkMLString(final String inkmlStrParam) throws InkMLException {
        final String inkmlStr;
        if (inkmlStrParam.indexOf("ink") == -1) {
            // the given welformed inkmlStr does not contain complete <ink> document as string.
            // wrap it with a false root element, <inkMLFramgment>. It is called as fragment.
            inkmlStr = "<inkMLFramgment>" + inkmlStrParam + " </inkMLFramgment>";
        } else {
            inkmlStr = inkmlStrParam;
        }
        InkMLDOMParser.LOG.fine(inkmlStr);

        try {
            final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            dbFactory.setIgnoringComments(true);
            dbFactory.setIgnoringElementContentWhitespace(true);
            dbFactory.setValidating(false);
            InkMLDOMParser.LOG.info("Validation using schema is disabled.");
            final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            this.inkmlDOMDocument = dBuilder.parse(inkmlStr);
            this.bindData(this.inkMLProcessor.getInk());
        } catch (final ParserConfigurationException e) {
            throw new InkMLException("Error in parsing Input InkML XML file.\n Message: " + e.getMessage(), e);
        } catch (final SAXException e) {
            throw new InkMLException("Error in parsing Input InkML XML file.\n Message: " + e.getMessage(), e);
        } catch (final IOException e) {
            throw new InkMLException("I/O error while parsing Input InkML XML file.\n Message: " + e.getMessage(),
                    e);
        }
    }

    /** Prints the error message. */
    private void printError(final String messageType, final SAXParseException exception) {

        System.err.print("[");
        System.err.print(messageType);
        System.err.print("] ");
        String systemId = exception.getSystemId();
        if (systemId != null) {
            final int index = systemId.lastIndexOf('/');
            if (index != -1) {
                systemId = systemId.substring(index + 1);
            }
            System.err.print(systemId);
        }
        System.err.print(':');
        System.err.print(exception.getLineNumber());
        System.err.print(':');
        System.err.print(exception.getColumnNumber());
        System.err.print(": ");
        System.err.print(exception.getMessage());
        System.err.println();
        System.err.flush();

    }

    // printError(String,SAXParseException) functions
    /**
     * Method to display XML Parse and schema validation error messages
     */
    @Override
    public void error(final SAXParseException exception) throws SAXException {
        this.printError("Error", exception);
        System.exit(-1);
    }

    /**
     * Method to display XML Parse and schema validation fatalError messages
     */
    @Override
    public void fatalError(final SAXParseException exception) throws SAXException {
        this.printError("FatalError", exception);
        System.exit(-1);
    }

    /**
     * Method to display XML Parse and schema validation warning messages
     */
    @Override
    public void warning(final SAXParseException exception) throws SAXException {
        this.printError("Warning", exception);
    }
}