Package nux.xom.xquery

Source Code of nux.xom.xquery.XQueryUtil

/*
* Copyright (c) 2005, The Regents of the University of California, through
* Lawrence Berkeley National Laboratory (subject to receipt of any required
* approvals from the U.S. Dept. of Energy). All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* (1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* (2) Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* (3) Neither the name of the University of California, Lawrence Berkeley
* National Laboratory, U.S. Dept. of Energy nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* You are under no obligation whatsoever to provide any bug fixes, patches, or
* upgrades to the features, functionality or performance of the source code
* ("Enhancements") to anyone; however, if you choose to make your Enhancements
* available either publicly, or directly to Lawrence Berkeley National
* Laboratory, without imposing a separate written license agreement for such
* Enhancements, then you hereby grant the following license: a non-exclusive,
* royalty-free perpetual license to install, use, modify, prepare derivative
* works, incorporate into other computer software, distribute, and sublicense
* such enhancements or derivative works thereof, in binary and source code
* form.
*/
package nux.xom.xquery;

import java.util.HashSet;
import java.util.Map;

import nu.xom.Attribute;
import nu.xom.Comment;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Node;
import nu.xom.Nodes;
import nu.xom.ParentNode;
import nu.xom.ProcessingInstruction;
import nu.xom.Text;
import nux.xom.pool.XQueryPool;

/**
* Various utilities avoiding redundant code in several classes.
*
* @author whoschek.AT.lbl.DOT.gov
* @author $Author: hoschek3 $
* @version $Revision: 1.34 $, $Date: 2006/03/11 06:52:48 $
*/
public class XQueryUtil {
 
  private static final Nodes EMPTY = new Nodes();
   
  private XQueryUtil() {} // not instantiable
   
  /**
   * Executes the given W3C XQuery or XPath against the given context node
   * (subtree); convenience method. Equivalent to
   *
   * <pre>
   * return XQueryPool.GLOBAL_POOL.getXQuery(query, null).execute(contextNode).toNodes();
   * </pre>
   *
   * Example usage:
   * <pre>
   * // find the atom named 'Zinc' in the periodic table:
   * Document doc = new Builder().build(new File("samples/data/periodic.xml"));
   * Node result = XQueryUtil.xquery(doc, "/PERIODIC_TABLE/ATOM[NAME = 'Zinc']").get(0);
   * System.out.println("result=" + result.toXML());
   * </pre>
   *
   * @param contextNode
   *            the context node to execute the query against. The context
   *            node is available to the query as the value of the query
   *            expression ".". If this parameter is <code>null</code>, the
   *            context node will be undefined.
   * @param query
   *            the XQuery or XPath string
   * @return the nodes of the result sequence.
   * @throws RuntimeException
   *             if an XQueryException occurs (unchecked exception for
   *             convenience)
   * @see XQuery
   * @see XQueryPool
   */
  public static Nodes xquery(Node contextNode, String query) {
    try {
      return XQueryPool.GLOBAL_POOL.getXQuery(query, null).execute(contextNode).toNodes();
    } catch (XQueryException e) { // part of the "convenience"
      throw new RuntimeException(e);
    }
  }

  /**
   * EXPERIMENTAL; Simple yet powerful and efficient in-place <i>morphing </i> for use as an
   * XQuery/XPath insert, update and delete facility; particularly useful for
   * structurally small tree transformations without requiring (potentially
   * huge) XML tree copies. Morphing is not in general intended as a
   * replacement for constructive XQuery or XSLT.
   * <p>
   * To get started, see the <a href="doc-files/update-examples.txt">example
   * use cases </a> and observe that various types of insert, update and
   * delete can be concisely expressed via morphing.
   * <p>
   * The morphing algorithm works as follows: For each node <code>N</code>
   * in the given <code>nodes</code> sequence, let <code>results</code> be
   * the node sequence returned by <code>morpher.execute(N, null, vars)</code>.
   * Now...
   * <ul>
   * <li>If <code>results</code> is the empty sequence then <code>N</code>
   * is detached (i.e. deleted) from its parent.</li>
   *
   * <li>Otherwise, let <code>atomic</code> be the string concatenation of
   * the standard XPath string value of all atomic values in
   * <code>results</code> (separating string values by a single space character),
   * and let <code>nonAtomic</code> be the list of
   * all non-atomic items in <code>results</code>. If there is at least one
   * atomic value, replace the content of <code>N</code> with
   * <code>atomic</code>. If there is at least one non-atomic value,
   * replace <code>N</code> with <code>nonAtomic</code>, updating the
   * parent of <code>N</code>.</li>
   * </ul>
   * <p>
   * <a target="_blank"
   * href="http://www.w3.org/TR/xpath-functions/#datatypes">Atomic types </a>
   * include xs:string, xs:integer, xs:double, xs:boolean, etc, all of which
   * are not XML nodes.
   * <p>
   * Note that if a morpher result sequence contains multiple
   * identical nodes, copies of those nodes will be made.
   * <p>
   * Example usage:
   * <pre>
   *     // read document from file
   *     Document doc = new Builder().build(new File("samples/data/articles.xml"));
   *
   *     // make all articles a bit cheaper
   *     XQueryUtil.update(doc, "//article/prize", ". * 0.95");
   *
   *     // delete all chairs
   *     XQueryUtil.update(doc, "//article[@name='chair']", "()");
   *
   *     // write updated document to file
   *     FileOutputStream out = new FileOutputStream("samples/data/articles2.xml");
   *     Serializer ser = new Serializer(out);
   *     ser.write(doc);
   *     out.close();
   * </pre>
   *
   * @param nodes
   *            the list of nodes to morph
   * @param morpher
   *            an XQuery or XPath query morphing each node in
   *            <code>nodes</code> into a new form (may be <code>null</code>
   *            in which case all nodes will be detached, i.e. deleted)
   * @param variables
   *            the morpher's external global variables (may be
   *            <code>null</code>).
   * @throws RuntimeException
   *             if an XQueryException occurs (unchecked exception for
   *             convenience)
   */
  public static void update(Nodes nodes, XQuery morpher, Map variables) {
    if (nodes == null)
      throw new IllegalArgumentException("nodes must not be null");
   
    /*
     * It is recommended (but not strictly necessary) that parameter
     * <code>nodes</code> does not contain duplicates wrt. node identity.
     */
    HashSet identities = null;
    for (int i=nodes.size(); --i >= 0; ) {
      Node node = nodes.get(i);
      Nodes results = EMPTY;
      if (morpher != null) {
        try {
          results = morpher.execute(node, null, variables).toNodes();
        } catch (XQueryException e) { // part of the "convenience"
          throw new RuntimeException(e);
        }
      }
     
      int size = results.size();
      if (size == 0) { // pure delete?
        node.detach();
        continue; // not really needed; just for clarity
      }
     
      if (size == 1 && node == results.get(0)) {
        continue; // nothing to do (replace X with X)
      }

      ParentNode parent = node.getParent();
      StringBuffer atomics = null;
      boolean isInitialized = false;
      int position = 0;
      if (size > 1) {
        if (identities == null) {
          identities = new HashSet();
        } else {
          identities.clear();
        }
      }
     
      for (int j=0; j < size; j++) {
        Node result = results.get(j);       
        if (DefaultResultSequence.isAtomicValue(result)) { // concat atomic values
          String value = result.getValue();
          if (atomics == null) {
            atomics = new StringBuffer(value.length());
          } else {
            atomics.append(' ');
          }
          atomics.append(value);
        } else if (parent != null) {
          if (size > 1 && !identities.add(result)) {
            result = result.copy(); // multiple identical nodes in results
//            throw new MultipleParentException(
//            "XQuery morpher result sequence must not contain multiple identical nodes");
          }
          boolean isRoot = parent instanceof Document && node instanceof Element;
          if (!isInitialized) {
            if (!(node instanceof Attribute)) position = parent.indexOf(node);
            if (!isRoot) node.detach();
            isInitialized = true;
          }
         
          if (result instanceof Attribute) {
            result.detach();
            ((Element) parent).addAttribute((Attribute)result);
          } else {
            if (isRoot && result instanceof Element) {
              parent.replaceChild(node, result);
            } else {
              result.detach();
              parent.insertChild(result, position);
            }
            position++;
          }
        }
      }
     
      if (atomics != null) { // found at least one atomic value?
        setValue(node, atomics.toString());
      }
    }
  }
 
  /** sets the XPath value of the given node to the given value */
  private static void setValue(Node node, String value) {
    if (node instanceof Document) {
      // remove all children except root element (XOM docs must have a root element)
      Document doc = (Document) node;
      Element root = doc.getRootElement();
      for (int k = doc.getChildCount(); --k >= 0; ) {
        if (doc.getChild(k) != root) doc.removeChild(k);
      }
      node = root; // replace root element's content
    }

    if (node instanceof Element) {
      Element elem = (Element) node;
      elem.removeChildren();
      elem.appendChild(value);
    } else if (node instanceof Attribute) {
      ((Attribute) node).setValue(value);
    } else if (node instanceof Text) {
      ((Text) node).setValue(value);
    } else if (node instanceof Comment) {
      ((Comment) node).setValue(value);
    } else if (node instanceof ProcessingInstruction) {
      ((ProcessingInstruction) node).setValue(value);
    } else {
      // ignore DocType (XPath spec has no concept of a DocType)
      // ignore Namespace (Saxon XQuery doesn't support them anymore)
    }   
  }

  /**
   * EXPERIMENTAL; Convenience morphing method. Equivalent to
   *
   * <pre>
   * XQuery xmorpher = XQueryPool.GLOBAL_POOL.getXQuery(morpher, null);
   * update(xquery(contextNode, select), xmorpher, null);
   * </pre>
   *
   * @param contextNode
   *            the context node to execute the select query against
   * @param select
   *            an XQuery or XPath query selecting the nodes to morph.
   * @param morpher
   *            an XQuery or XPath query morphing each node in
   *            <code>xquery(contextNode, select)</code> into a new form
   *            (may be <code>null</code> in which case all nodes will be
   *            detached, i.e. deleted)
   * @throws RuntimeException
   *             if an XQueryException occurs (unchecked exception for
   *             convenience)
   */
  public static void update(Node contextNode, String select, String morpher) {
    if (contextNode == null)
      throw new IllegalArgumentException("contextNode must not be null");
    if (select == null)
      throw new IllegalArgumentException("select must not be null");
   
    XQuery xmorpher = null;
    if (morpher != null && !morpher.trim().equals("()")) {
      try {
        xmorpher = XQueryPool.GLOBAL_POOL.getXQuery(morpher, null);
      } catch (XQueryException e) { // part of the "convenience"
        throw new RuntimeException(e);
      }
    }
   
    Nodes nodes = xquery(contextNode, select);
    update(nodes, xmorpher, null);
  }
 
  /** little helper for safe reading of boolean system properties */
  static boolean getSystemProperty(String key, boolean defaults) {
    try {
      return "true".equalsIgnoreCase(
          System.getProperty(key, String.valueOf(defaults)));
    } catch (Throwable e) { // better safe than sorry (applets, security managers, etc.) ...
      return defaults; // we can live with that
    }   
  }
 
//  // TODO
//  public static Element xqueryFirstElement(Node contextNode, String query) {
//    Nodes results = xquery(contextNode, query);
//    switch (results.size()) {
//      case 0: return null;
//      case 1: return (Element) results.get(0);
//      default: throw new IllegalStateException(
//          "result sequence must not contain more than one element; " +
//          "but contains " + results.size() + " nodes");
//    }
//  } 

}
TOP

Related Classes of nux.xom.xquery.XQueryUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.