All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ow2.bonita.util.DebugDomBuilder Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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
 *
 * 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: DebugDomBuilder.java 1434 2008-07-01 10:32:10Z [email protected] $
 */
package org.ow2.bonita.util;

import java.util.Stack;
import java.util.Vector;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
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.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.DefaultHandler;

/**
 * builds the dom model from SAX events, optionally adding the line and column number as attributes to every element.
 */
class DebugDomBuilder extends DefaultHandler implements ContentHandler, LexicalHandler {
  /** Root document */

  public Document document;

  protected String debugNamespace = null;
  protected String lineAttributeName = "line";
  protected String columnAttributeName = null;

  /** Current node */
  protected Node currentNode = null;

  /** The root node */
  protected Node root = null;

  /** The next sibling node */
  protected Node nextSibling = null;

  /** First node of document fragment or null if not a DocumentFragment */
  public DocumentFragment docFrag = null;

  /** Vector of element nodes */
  @SuppressWarnings("rawtypes")
  protected Stack elemStack = new Stack();

  /** Namespace support */
  @SuppressWarnings("rawtypes")
  protected Vector prefixMappings = new Vector();

  /** to obtain the line number information */
  protected Locator locator = null;

  /**
   * Get the root document or DocumentFragment of the DOM being created.
   * 
   * @return The root document or document fragment if not null
   */
  public Node getRootDocument() {
    return null != docFrag ? (Node) docFrag : (Node) document;
  }

  /**
   * Get the root node of the DOM tree.
   */
  public Node getRootNode() {
    return root;
  }

  /**
   * Get the node currently being processed.
   * 
   * @return the current node being processed
   */
  public Node getCurrentNode() {
    return currentNode;
  }

  /**
   * Set the next sibling node, which is where the result nodes should be inserted before.
   * 
   * @param nextSibling
   *          the next sibling node.
   */
  public void setNextSibling(final Node nextSibling) {
    this.nextSibling = nextSibling;
  }

  /**
   * Return the next sibling node.
   * 
   * @return the next sibling node.
   */
  public Node getNextSibling() {
    return nextSibling;
  }

  /**
   * Return null since there is no Writer for this class.
   * 
   * @return null
   */
  public java.io.Writer getWriter() {
    return null;
  }

  /**
   * Append a node to the current container.
   * 
   * @param newNode
   *          New node to append
   */
  protected void append(final Node newNode) throws SAXException {

    final Node currentNode = this.currentNode;

    if (null != currentNode) {
      if (currentNode == root && nextSibling != null) {
        currentNode.insertBefore(newNode, nextSibling);
      } else {
        currentNode.appendChild(newNode);
      }

    } else if (null != docFrag) {
      if (nextSibling != null) {
        docFrag.insertBefore(newNode, nextSibling);
      } else {
        docFrag.appendChild(newNode);
      }
    } else {
      boolean ok = true;
      final short type = newNode.getNodeType();

      if (type == Node.TEXT_NODE) {
        final String data = newNode.getNodeValue();

        if (null != data && data.trim().length() > 0) {
          final String message = ExceptionManager.getInstance().getFullMessage("bp_DDB_1");
          throw new SAXException(message);
        }

        ok = false;
      } else if (type == Node.ELEMENT_NODE) {
        if (document.getDocumentElement() != null) {
          ok = false;
          final String message = ExceptionManager.getInstance().getFullMessage("bp_DDB_2");
          throw new SAXException(message);
        }
      }

      if (ok) {
        if (nextSibling != null) {
          document.insertBefore(newNode, nextSibling);
        } else {
          document.appendChild(newNode);
        }
      }
    }
  }

  /**
   * Receive an object for locating the origin of SAX document events.
   * 
   * 

* SAX parsers are strongly encouraged (though not absolutely required) to supply a locator: if it does so, it must * supply the locator to the application by invoking this method before invoking any of the other methods in the * ContentHandler interface. *

* *

* The locator allows the application to determine the end position of any document-related event, even if the parser * is not reporting an error. Typically, the application will use this information for reporting its own errors (such * as character content that does not match an application's business rules). The information returned by the locator * is probably not sufficient for use with a search engine. *

* *

* Note that the locator will return correct information only during the invocation of the events in this interface. * The application should not attempt to use it at any other time. *

* * @param locator * An object that can return the location of any SAX document event. * @see Locator */ @Override public void setDocumentLocator(final Locator locator) { this.locator = locator; // No action for the moment. } /** * Receive notification of the beginning of a document. * *

* The SAX parser will invoke this method only once, before any other methods in this interface or in DTDHandler * (except for setDocumentLocator). *

*/ @Override public void startDocument() throws SAXException { // No action for the moment. } /** * Receive notification of the end of a document. * *

* The SAX parser will invoke this method only once, and it will be the last method invoked during the parse. The * parser shall not invoke this method until it has either abandoned parsing (because of an unrecoverable error) or * reached the end of input. *

*/ @Override public void endDocument() throws SAXException { // No action for the moment. } /** * Receive notification of the beginning of an element. * *

* The Parser will invoke this method at the beginning of every element in the XML document; there will be a * corresponding endElement() event for every startElement() event (even when the element is empty). All of the * element's content will be reported, in order, before the corresponding endElement() event. *

* *

* If the element name has a namespace prefix, the prefix will still be attached. Note that the attribute list * provided will contain only attributes with explicit values (specified or defaulted): #IMPLIED attributes will be * omitted. *

* * * @param ns * The namespace of the node * @param localName * The local part of the qualified name * @param name * The element name. * @param atts * The attributes attached to the element, if any. * @see #endElement * @see Attributes */ @Override @SuppressWarnings("unchecked") public void startElement(final String ns, final String localName, final String name, final Attributes atts) throws SAXException { Element elem; // Note that the namespace-aware call must be used to correctly // construct a Level 2 DOM, even for non-namespaced nodes. if (null == ns || ns.length() == 0) { elem = document.createElementNS(null, name); } else { elem = document.createElementNS(ns, name); } append(elem); try { final int nAtts = atts.getLength(); if (0 != nAtts) { for (int i = 0; i < nAtts; i++) { // First handle a possible ID attribute if (atts.getType(i).equalsIgnoreCase("ID")) { setIDAttribute(atts.getValue(i), elem); } String attrNS = atts.getURI(i); if ("".equals(attrNS)) { attrNS = null; // DOM represents no-namespace as null } // Crimson won't let us set an xmlns: attribute on the DOM. final String attrQName = atts.getQName(i); // In SAX, xmlns[:] attributes have an empty namespace, while in DOM // they // should have the xmlns namespace if (attrQName.startsWith("xmlns:") || "xmlns".equals(attrQName)) { attrNS = "http://www.w3.org/2000/xmlns/"; } // ALWAYS use the DOM Level 2 call! elem.setAttributeNS(attrNS, attrQName, atts.getValue(i)); } } if (locator != null) { final int lineNumber = locator.getLineNumber(); final int columnNumber = locator.getColumnNumber(); if (debugNamespace == null) { if (lineAttributeName != null) { elem.setAttribute(lineAttributeName, Integer.toString(lineNumber)); } if (columnAttributeName != null) { elem.setAttribute(columnAttributeName, Integer.toString(columnNumber)); } } else { if (lineAttributeName != null) { elem.setAttributeNS(debugNamespace, lineAttributeName, Integer.toString(lineNumber)); } if (columnAttributeName != null) { elem.setAttributeNS(debugNamespace, columnAttributeName, Integer.toString(columnNumber)); } } } /* * Adding namespace nodes to the DOM tree; */ final int nDecls = prefixMappings.size(); String prefix, declURL; for (int i = 0; i < nDecls; i += 2) { prefix = (String) prefixMappings.elementAt(i); if (prefix == null) { continue; } declURL = (String) prefixMappings.elementAt(i + 1); elem.setAttributeNS("http://www.w3.org/2000/xmlns/", prefix, declURL); } prefixMappings.clear(); elemStack.push(elem); currentNode = elem; } catch (final Exception de) { throw new SAXException(de); } } /** * * * * Receive notification of the end of an element. * *

* The SAX parser will invoke this method at the end of every element in the XML document; there will be a * corresponding startElement() event for every endElement() event (even when the element is empty). *

* *

* If the element name has a namespace prefix, the prefix will still be attached to the name. *

* * * @param ns * the namespace of the element * @param localName * The local part of the qualified name of the element * @param name * The element name */ @Override public void endElement(final String ns, final String localName, final String name) throws SAXException { elemStack.pop(); currentNode = elemStack.isEmpty() ? null : (Node) elemStack.peek(); } /** * Set an ID string to node association in the ID table. * * @param id * The ID string. * @param elem * The associated ID. */ public void setIDAttribute(final String id, final Element elem) { // Do nothing. This method is meant to be overiden. } /** * Receive notification of character data. * *

* The Parser will call this method to report each chunk of character data. SAX parsers may return all contiguous * character data in a single chunk, or they may split it into several chunks; however, all of the characters in any * single event must come from the same external entity, so that the Locator provides useful information. *

* *

* The application must not attempt to read from the array outside of the specified range. *

* *

* Note that some parsers will report whitespace using the ignorableWhitespace() method rather than this one * (validating parsers must do so). *

* * @param ch * The characters from the XML document. * @param start * The start position in the array. * @param length * The number of characters to read from the array. * @see #ignorableWhitespace * @see Locator */ @Override public void characters(final char ch[], final int start, final int length) throws SAXException { if (isOutsideDocElem() && isWhiteSpace(ch, start, length)) { return; // avoid DOM006 Hierarchy request error } if (inCData) { cdata(ch, start, length); return; } final String s = new String(ch, start, length); Node childNode; childNode = currentNode != null ? currentNode.getLastChild() : null; if (childNode != null && childNode.getNodeType() == Node.TEXT_NODE) { ((Text) childNode).appendData(s); } else { final Text text = document.createTextNode(s); append(text); } } /** * If available, when the disable-output-escaping attribute is used, output raw text without escaping. A PI will be * inserted in front of the node with the name "lotusxsl-next-is-raw" and a value of "formatter-to-dom". * * @param ch * Array containing the characters * @param start * Index to start of characters in the array * @param length * Number of characters in the array */ public void charactersRaw(final char ch[], final int start, final int length) throws SAXException { if (isOutsideDocElem() && isWhiteSpace(ch, start, length)) { return; // avoid DOM006 Hierarchy request error } final String s = new String(ch, start, length); append(document.createProcessingInstruction("xslt-next-is-raw", "formatter-to-dom")); append(document.createTextNode(s)); } /** * Report the beginning of an entity. * * The start and end of the document entity are not reported. The start and end of the external DTD subset are * reported using the pseudo-name "[dtd]". All other events must be properly nested within start/end entity events. * * @param name * The name of the entity. If it is a parameter entity, the name will begin with '%'. * @see #endEntity * @see ext.DeclHandler#internalEntityDecl * @see ext.DeclHandler#externalEntityDecl */ @Override public void startEntity(final String name) throws SAXException { // Almost certainly the wrong behavior... // entityReference(name); } /** * Report the end of an entity. * * @param name * The name of the entity that is ending. * @see #startEntity */ @Override public void endEntity(final String name) throws SAXException { } /** * Receive notivication of a entityReference. * * @param name * name of the entity reference */ public void entityReference(final String name) throws SAXException { append(document.createEntityReference(name)); } /** * Receive notification of ignorable whitespace in element content. * *

* Validating Parsers must use this method to report each chunk of ignorable whitespace (see the W3C XML 1.0 * recommendation, section 2.10): non-validating parsers may also use this method if they are capable of parsing and * using content models. *

* *

* SAX parsers may return all contiguous whitespace in a single chunk, or they may split it into several chunks; * however, all of the characters in any single event must come from the same external entity, so that the Locator * provides useful information. *

* *

* The application must not attempt to read from the array outside of the specified range. *

* * @param ch * The characters from the XML document. * @param start * The start position in the array. * @param length * The number of characters to read from the array. * @see #characters */ @Override public void ignorableWhitespace(final char ch[], final int start, final int length) throws SAXException { if (isOutsideDocElem()) { return; // avoid DOM006 Hierarchy request error } final String s = new String(ch, start, length); append(document.createTextNode(s)); } /** * Tell if the current node is outside the document element. * * @return true if the current node is outside the document element. */ private boolean isOutsideDocElem() { return null == docFrag && elemStack.size() == 0 && (null == currentNode || currentNode.getNodeType() == Node.DOCUMENT_NODE); } /** * Receive notification of a processing instruction. * *

* The Parser will invoke this method once for each processing instruction found: note that processing instructions * may occur before or after the main document element. *

* *

* A SAX parser should never report an XML declaration (XML 1.0, section 2.8) or a text declaration (XML 1.0, section * 4.3.1) using this method. *

* * @param target * The processing instruction target. * @param data * The processing instruction data, or null if none was supplied. */ @Override public void processingInstruction(final String target, final String data) throws SAXException { append(document.createProcessingInstruction(target, data)); } /** * Report an XML comment anywhere in the document. * * This callback will be used for comments inside or outside the document element, including comments in the external * DTD subset (if read). * * @param ch * An array holding the characters in the comment. * @param start * The starting position in the array. * @param length * The number of characters to use from the array. */ @Override public void comment(final char ch[], final int start, final int length) throws SAXException { append(document.createComment(new String(ch, start, length))); } /** Flag indicating that we are processing a CData section */ protected boolean inCData = false; /** * Report the start of a CDATA section. * * @see #endCDATA */ @Override public void startCDATA() throws SAXException { inCData = true; append(document.createCDATASection("")); } /** * Report the end of a CDATA section. * * @see #startCDATA */ @Override public void endCDATA() throws SAXException { inCData = false; } /** * Receive notification of cdata. * *

* The Parser will call this method to report each chunk of character data. SAX parsers may return all contiguous * character data in a single chunk, or they may split it into several chunks; however, all of the characters in any * single event must come from the same external entity, so that the Locator provides useful information. *

* *

* The application must not attempt to read from the array outside of the specified range. *

* *

* Note that some parsers will report whitespace using the ignorableWhitespace() method rather than this one * (validating parsers must do so). *

* * @param ch * The characters from the XML document. * @param start * The start position in the array. * @param length * The number of characters to read from the array. * @see #ignorableWhitespace * @see Locator */ public void cdata(final char ch[], final int start, final int length) throws SAXException { if (isOutsideDocElem() && isWhiteSpace(ch, start, length)) { return; // avoid DOM006 Hierarchy request error } final String s = new String(ch, start, length); final CDATASection section = (CDATASection) currentNode.getLastChild(); section.appendData(s); } /** * Report the start of DTD declarations, if any. * * Any declarations are assumed to be in the internal subset unless otherwise indicated. * * @param name * The document type name. * @param publicId * The declared public identifier for the external DTD subset, or null if none was declared. * @param systemId * The declared system identifier for the external DTD subset, or null if none was declared. * @see #endDTD * @see #startEntity */ @Override public void startDTD(final String name, final String publicId, final String systemId) throws SAXException { // Do nothing for now. } /** * Report the end of DTD declarations. * * @see #startDTD */ @Override public void endDTD() throws SAXException { // Do nothing for now. } /** * Begin the scope of a prefix-URI Namespace mapping. * *

* The information from this event is not necessary for normal Namespace processing: the SAX XML reader will * automatically replace prefixes for element and attribute names when the http://xml.org/sax/features/namespaces * feature is true (the default). *

* *

* There are cases, however, when applications need to use prefixes in character data or in attribute values, where * they cannot safely be expanded automatically; the start/endPrefixMapping event supplies the information to the * application to expand prefixes in those contexts itself, if necessary. *

* *

* Note that start/endPrefixMapping events are not guaranteed to be properly nested relative to each-other: all * startPrefixMapping events will occur before the corresponding startElement event, and all endPrefixMapping events * will occur after the corresponding endElement event, but their order is not guaranteed. *

* * @param prefix * The Namespace prefix being declared. * @param uri * The Namespace URI the prefix is mapped to. * @see #endPrefixMapping * @see #startElement */ @Override @SuppressWarnings("unchecked") public void startPrefixMapping(String prefix, final String uri) throws SAXException { if (null == prefix || prefix.equals("")) { prefix = "xmlns"; } else { prefix = "xmlns:" + prefix; } prefixMappings.addElement(prefix); prefixMappings.addElement(uri); } /** * End the scope of a prefix-URI mapping. * *

* See startPrefixMapping for details. This event will always occur after the corresponding endElement event, but the * order of endPrefixMapping events is not otherwise guaranteed. *

* * @param prefix * The prefix that was being mapping. * @see #startPrefixMapping * @see #endElement */ @Override public void endPrefixMapping(final String prefix) throws SAXException { } /** * Receive notification of a skipped entity. * *

* The Parser will invoke this method once for each entity skipped. Non-validating processors may skip entities if * they have not seen the declarations (because, for example, the entity was declared in an external DTD subset). All * processors may skip external entities, depending on the values of the * http://xml.org/sax/features/external-general-entities and the * http://xml.org/sax/features/external-parameter-entities properties. *

* * @param name * The name of the skipped entity. If it is a parameter entity, the name will begin with '%'. */ @Override public void skippedEntity(final String name) throws SAXException { } /** * Returns whether the specified ch conforms to the XML 1.0 definition of whitespace. Refer to the definition of S for details. * * @param ch * Character to check as XML whitespace. * @return =true if ch is XML whitespace; otherwise =false. */ public static boolean isWhiteSpace(final char ch) { return ch == 0x20 || ch == 0x09 || ch == 0xD || ch == 0xA; } /** * Tell if the string is whitespace. * * @param ch * Character array to check as XML whitespace. * @param start * Start index of characters in the array * @param length * Number of characters in the array * @return True if the characters in the array are XML whitespace; otherwise, false. */ public static boolean isWhiteSpace(final char ch[], final int start, final int length) { final int end = start + length; for (int s = start; s < end; s++) { if (!isWhiteSpace(ch[s])) { return false; } } return true; } public void setDebugNamespace(final String debugNamespace) { this.debugNamespace = debugNamespace; } public void setLineAttributeName(final String lineAttributeName) { this.lineAttributeName = lineAttributeName; } public void setColumnAttributeName(final String columnAttributeName) { this.columnAttributeName = columnAttributeName; } public String getDebugNamespace() { return debugNamespace; } public String getLineAttributeName() { return lineAttributeName; } public String getColumnAttributeName() { return columnAttributeName; } public Document getDocument() { return document; } public void setDocument(final Document document) { this.document = document; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy