OdfFileSaxHandler.java

/**
 * **********************************************************************
 *
 * <p>DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * <p>Copyright 2008, 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * <p>Use is subject to license terms.
 *
 * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0. You can also obtain a copy of the License at
 * http://odftoolkit.org/docs/license.txt
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 *
 * <p>See the License for the specific language governing permissions and limitations under the
 * License.
 *
 * <p>**********************************************************************
 */
package org.odftoolkit.odfdom.pkg;

import java.io.IOException;
import java.util.Stack;
import org.odftoolkit.odfdom.pkg.rdfa.JenaSink;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class OdfFileSaxHandler extends DefaultHandler {

  private static final String EMPTY_STRING = "";
  // the empty XML file to which nodes will be added
  private OdfFileDom mFileDom;
  // the context node
  protected Node
      mCurrentNode; // a stack of sub handlers. handlers will be pushed on the stack whenever
  // they are required and must pop themselves from the stack when done
  private Stack<ContentHandler> mHandlerStack = new Stack<ContentHandler>();
  private StringBuilder mCharsForTextNode = new StringBuilder();
  private JenaSink sink;

  public OdfFileSaxHandler(Node rootNode) throws SAXException {
    if (rootNode instanceof OdfFileDom) {
      mFileDom = (OdfFileDom) rootNode;
    } else {
      mFileDom = (OdfFileDom) rootNode.getOwnerDocument();
    }
    mCurrentNode = rootNode;
  }

  @Override
  public void startDocument() throws SAXException {}

  @Override
  public void endDocument() throws SAXException {}

  @Override
  public void endElement(String uri, String localName, String qName) throws SAXException {
    flushTextNode();
    // pop to the parent node
    mCurrentNode = mCurrentNode.getParentNode();
  }

  @Override
  public void startElement(String uri, String localName, String qName, Attributes attributes)
      throws SAXException {
    flushTextNode();
    // if there is a specilized handler on the stack, dispatch the event
    Element element = null;
    if (uri.equals(EMPTY_STRING) || qName.equals(EMPTY_STRING)) {
      element = mFileDom.createElement(localName);
    } else {
      // if localName is the same object as qName, there is a default namespace set
      if (localName == qName) {
        element =
            mFileDom.createElementNS(
                OdfName.getOdfName(OdfNamespace.newNamespace(null, uri), localName));
      } else {
        element = mFileDom.createElementNS(uri, qName);
      }
    }
    String attrQname = null;
    String attrURL = null;
    OdfAttribute attr = null;
    for (int i = 0; i < attributes.getLength(); i++) {
      attrURL = attributes.getURI(i);
      attrQname = attributes.getQName(i);
      // if no namespace exists
      if (attrURL.equals(EMPTY_STRING) || attrQname.equals(EMPTY_STRING)) {
        // create attribute without prefix
        attr = mFileDom.createAttribute(attributes.getLocalName(i));
      } else {
        if (attrQname.startsWith("xmlns:")) {
          // in case of xmlns prefix we have to create a new OdfNamespace
          OdfNamespace namespace =
              mFileDom.setNamespace(attributes.getLocalName(i), attributes.getValue(i));
          // if the file Dom is already associated to parsed XML add the new namespace to the root
          // element
          Element root = mFileDom.getRootElement();
          if (root == null) {
            root = element;
          }
          root.setAttributeNS(
              "http://www.w3.org/2000/xmlns/",
              "xmlns:" + namespace.getPrefix(),
              namespace.getUri());
        } else if (attrQname.equals("xmlns")) {
          continue; // skip the name space default attribute
        }
        // create all attributes, even namespace attributes
        attr = mFileDom.createAttributeNS(attrURL, attrQname);
      }

      // namespace attributes will not be created and return null
      if (attr != null) {
        element.setAttributeNodeNS(attr);
        // don't exit because of invalid attribute values
        try {
          // set Value in the attribute to allow validation in the attribute
          attr.setValue(attributes.getValue(i));
        } // if we detect an attribute with invalid value: remove attribute node
        catch (IllegalArgumentException e) {
          element.removeAttributeNode(attr);
        }
      }
    }
    // add the new element as a child of the current context node
    mCurrentNode.appendChild(element);
    // push the new element as the context node...
    mCurrentNode = element;
    if (!localName.equals("bookmark-start")) {
      setContextNode(mCurrentNode);
    }
  }

  /**
   * http://xerces.apache.org/xerces2-j/faq-sax.html#faq-2 : SAX may deliver contiguous text as
   * multiple calls to characters, for reasons having to do with parser efficiency and input
   * buffering. It is the programmer's responsibility to deal with that appropriately, e.g. by
   * accumulating text until the next non-characters event.
   */
  protected void flushTextNode() {
    if (mCharsForTextNode.length() > 0) {
      Text text = mFileDom.createTextNode(mCharsForTextNode.toString());
      mCurrentNode.appendChild(text);
      mCharsForTextNode.setLength(0);
    }
  }

  @Override
  public void characters(char[] ch, int start, int length) throws SAXException {
    if (!mHandlerStack.empty()) {
      mHandlerStack.peek().characters(ch, start, length);
    } else {
      mCharsForTextNode.append(ch, start, length);
    }
  }

  @Override
  public InputSource resolveEntity(String publicId, String systemId)
      throws IOException, SAXException {
    return super.resolveEntity(publicId, systemId);
  }

  /**
   * Expose the current node to JenaSink to for caching the parsed RDF triples.
   *
   * @return
   */
  protected void setContextNode(Node node) {
    if (this.sink != null) {
      sink.setContextNode(node);
    }
  }

  /**
   * Set the JenaSink object.
   *
   * @param sink
   */
  public void setSink(JenaSink sink) {
    this.sink = sink;
  }
}