Package dk.brics.xact.operations

Source Code of dk.brics.xact.operations.XMLNavigator$ElementListResult

package dk.brics.xact.operations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Stack;

import org.jaxen.BaseXPath;
import org.jaxen.DefaultNavigator;
import org.jaxen.JaxenConstants;
import org.jaxen.JaxenException;
import org.jaxen.JaxenRuntimeException;
import org.jaxen.NamespaceContext;
import org.jaxen.UnsupportedAxisException;
import org.jaxen.XPath;
import org.jaxen.function.StringFunction;
import org.jaxen.saxpath.SAXPathException;

import dk.brics.xact.AttrNode;
import dk.brics.xact.Element;
import dk.brics.xact.Node;
import dk.brics.xact.TempNode;
import dk.brics.xact.XML;
import dk.brics.xact.XMLXPathException;
import dk.brics.xact.wrappers.AttrNodeWrapper;
import dk.brics.xact.wrappers.AttributeGapWrapper;
import dk.brics.xact.wrappers.AttributeWrapper;
import dk.brics.xact.wrappers.CommentWrapper;
import dk.brics.xact.wrappers.ConcreteTempNodeWrapper;
import dk.brics.xact.wrappers.ElementWrapper;
import dk.brics.xact.wrappers.NodeWrapper;
import dk.brics.xact.wrappers.ProcessingInstructionWrapper;
import dk.brics.xact.wrappers.TempNodeWrapper;
import dk.brics.xact.wrappers.TemplateGapWrapper;
import dk.brics.xact.wrappers.TextWrapper;
import dk.brics.xact.wrappers.WrapperNodeVisitor;


/**
* XPath evaluator for XML templates.
* <p>
* Traversal uses mutable node wrappers (see {@link NodeWrapper}) such that the
* DAG gets expanded to a tree by need. Visited nodes are copied, so all selected
* nodes are guaranteed to be fresh objects.
*/
public class XMLNavigator {
 
  private XMLNavigator() {}
 
  /**
   * Abstract result of evaluation.
   */
  public static class Result {

    private final XML initial;
   
    /**
     * Constructs a new result.
     */
    Result(XML initial) {
      this.initial = initial;
    }
   
    /**
     * Returns the copy of the real initial context.
     */
    public XML getInitial() {
      return initial;
    }
  }
 
  /**
   * Single-node result.
   */
  public static class NodeResult extends Result {

    private final Node node;
   
    /**
     * Constructs a new result.
     */
    public NodeResult(XML root, Node node) {
      super(root);
      this.node = node;
    }
   
    /**
     * Returns the selected node.
     */
    public Node getNode() {
      return node;
    }
  }
   
  /**
   * Node list result.
   */
  public static class NodeListResult extends Result {

    private final List<Node> nodes;
   
    /**
     * Constructs a new result.
     */
    public NodeListResult(XML root, List<Node> nodes) {
      super(root);
      this.nodes = nodes;
    }
   
    /**
     * Returns the selected nodes.
     */
    public List<Node> getNodes() {
      return nodes;
    }
  }
   
  /**
   * Element list result.
   */
  public static class ElementListResult extends Result {

    private final List<Element> elements;
   
    /**
     * Constructs a new result.
     */
    public ElementListResult(XML root, List<Element> elements) {
      super(root);
      this.elements = elements;
    }
   
    /**
     * Returns the selected elements.
     */
    public List<Element> getElements() {
      return elements;
    }
  }
   
  private static TempNode getRealFirstChild(ElementWrapper e) {
    ConcreteTempNodeWrapper<? extends TempNode> c =  e.getFirstChild();
    return c != null ? c.getReal() : null;
  }
 
  /**
   * Returns the selected nodes.
   */
  public static NodeListResult selectNodes(XML context, String xpath, boolean remove_successors) {
    try {
      ElementWrapper root = makeRoot(context);
      List<?> selected = prepare(xpath, root).selectNodes(root.getFirstChild())// apparently, jaxen doesn't use deep recursive calls
      List<Node> sel = copyNodes(root, selected, remove_successors);
      return new NodeListResult(getRealFirstChild(root), sel);
    } catch (JaxenException e) {
      throw new XMLXPathException(e);
    } catch (JaxenRuntimeException e) {
      throw new XMLXPathException(e.getCause());
    }
  }
 
  /**
   * Returns the selected sequence of element nodes (ignoring other nodes).
   */
  public static ElementListResult selectElements(XML context, String xpath, boolean remove_successors) {
    NodeListResult r = selectNodes(context, xpath, remove_successors);
    List<Element> es = new ArrayList<Element>();
    for (Node n : r.nodes)
      if (n.isElement())
        es.add(n.asElement());
    return new ElementListResult(r.getInitial(), es);
  }
 
  /**
   * Returns the string values of the selected nodes.
   */
  public static List<String> selectStrings(XML context, String xpath) {
    try {
      ElementWrapper root = makeRoot(context);
      List<?> selected = prepare(xpath, root).selectNodes(root.getFirstChild());
      List<String> res = new ArrayList<String>();
      MyNavigator nav = new MyNavigator(root);
      for (Object s : selected)
        res.add(StringFunction.evaluate(s, nav));
      return Collections.unmodifiableList(res);
    } catch (JaxenException e) {
      throw new XMLXPathException(e);
    } catch (JaxenRuntimeException e) {
      throw new XMLXPathException(e.getCause());
    }
  }
 
  /**
   * Returns the first selected node.
   */
  public static NodeResult selectSingleNode(XML context, String xpath, boolean remove_successors) {
    try {
      ElementWrapper root = makeRoot(context);
      List<Object> s = new ArrayList<Object>();
      Object n = prepare(xpath, root).selectSingleNode(root.getFirstChild());
      if (n == null)
        throw new XMLXPathException("no node selected");
      s.add(n);
      List<Node> sel = copyNodes(root, s, remove_successors);
      if (sel.isEmpty()) // root is removed by copyNodes if selected
        throw new XMLXPathException("no node selected");
      return new NodeResult(getRealFirstChild(root), sel.get(0));
    } catch (JaxenException e) {
      throw new XMLXPathException(e);
    } catch (JaxenRuntimeException e) {
      throw new XMLXPathException(e.getCause());
    }
  }

  /**
   * Returns the element node by the given ID.
   */
  public static Element selectElementByID(XML context, String id, boolean remove_successors) {
    // TODO: make a more robust solution than generating an xpath expression
    if (!isValidXPathString(id))
      throw new IllegalArgumentException(id + " is not a valid ID string");
    return (Element) selectSingleNode(context, "//node()[@id='" + id + "']" , remove_successors).getNode();
  }
 
  /**
   * Returns false if the given string contains a quotation mark ('single' or "double").
   */
  public static boolean isValidXPathString(String s) {
    for (int i=0; i<s.length(); i++) {
      switch (s.charAt(i)) {
      case '\'':
      case '"':
        return false;
      }
    }
    return true;
  }
 
  /**
   * Returns the string value of the first selected node.
   */
  public static String stringValueOf(XML context, String xpath) {
    try {
      ElementWrapper root = makeRoot(context);
      return prepare(xpath, root).stringValueOf(root.getFirstChild());
    } catch (JaxenException e) {
      throw new XMLXPathException(e);
    } catch (JaxenRuntimeException e) {
      throw new XMLXPathException(e.getCause());
    }
  }

  /**
   * Returns the boolean value of the first selected node.
   */
  public static boolean booleanValueOf(XML context, String xpath) {
    try {
      ElementWrapper root = makeRoot(context);
      return prepare(xpath, root).booleanValueOf(root.getFirstChild());
    } catch (JaxenException e) {
      throw new XMLXPathException(e);
    } catch (JaxenRuntimeException e) {
      throw new XMLXPathException(e.getCause());
    }
  }

  /**
   * Returns the number value of the first selected node.
   */
  public static Number numberValueOf(XML context, String xpath) {
    try {
      ElementWrapper root = makeRoot(context);
      return prepare(xpath, root).numberValueOf(root.getFirstChild());
    } catch (JaxenException e) {
      throw new XMLXPathException(e);
    } catch (JaxenRuntimeException e) {
      throw new XMLXPathException(e.getCause());
    }
  }
 
  private static ElementWrapper makeRoot(XML context) {
    return new ElementWrapper((Element)new Element("root").appendContent(context), null); // used as document root node
  }
 
  private static XPath prepare(String xpath, NodeWrapper<? extends Node> root) {
    try {
      MyNavigator nav = new MyNavigator(root);
      XPath xp = nav.parseXPath(xpath); // TODO: cache results of XPath parsing (per thread)?
      xp.setNamespaceContext(new NamespaceContext() {
        public String translateNamespacePrefixToUri(String prefix) {
          String ns = XML.getThreadNamespaceMap().get(prefix);
          if (ns == null)
            ns = XML.getNamespaceMap().get(prefix);
          return ns;
        }}
      );
      return xp;
    } catch (SAXPathException e) {
      throw new XMLXPathException("XPath error", e);
    }
  }
 
  @SuppressWarnings("serial")
  private static class MyNavigator extends DefaultNavigator {
   
    private final NodeWrapper<? extends Node> root;
   
    public MyNavigator(NodeWrapper<? extends Node> root) {
      this.root = root;
    }
   
    public XPath parseXPath(String xpath) throws SAXPathException {
      return new BaseXPath(xpath, this);
    }

    public boolean isAttribute(Object obj) {
      return obj instanceof AttrNodeWrapper<?>;
    }

    public boolean isComment(Object obj) {
      return obj instanceof CommentWrapper;
    }

    public boolean isDocument(Object obj) {
      return obj == root;
    }

    public boolean isElement(Object obj) {
      return obj instanceof ElementWrapper && obj != root;
    }

    public boolean isNamespace(Object obj) {
      return false; // unreachable (namespace axis not supported)
    }

    public boolean isProcessingInstruction(Object obj) {
      return obj instanceof ProcessingInstructionWrapper;
    }

    public boolean isText(Object obj) {
      return obj instanceof TextWrapper;
    }

    public String getAttributeName(Object attr) {
      return ((AttributeWrapper)attr).getReal().getLocalName();
    }

    public String getAttributeNamespaceUri(Object attr) {
      return ((AttributeWrapper)attr).getReal().getNamespace();
    }

    public String getAttributeQName(Object attr) {
      return getAttributeName(attr);
    }

    public String getAttributeStringValue(Object attr) {
      return ((AttributeWrapper)attr).getReal().getValue();
    }

    public String getCommentStringValue(Object comment) {
      return ((CommentWrapper)comment).getReal().getValue();
    }

    public String getElementName(Object element) {
      return ((ElementWrapper)element).getReal().getLocalName();
    }

    public String getElementNamespaceUri(Object element) {
      return ((ElementWrapper)element).getReal().getNamespace();
    }

    public String getElementQName(Object element) {
      return getElementName(element);
    }

    public String getElementStringValue(Object element) {
      return XMLPrinter.getElementStringValue(((ElementWrapper)element).getReal());
    }

    public String getNamespacePrefix(Object ns) {
      return ""; // unreachable (namespace axis not supported)
    }

    public String getNamespaceStringValue(Object ns) {
      return ""; // unreachable (namespace axis not supported)
    }

    public String getTextStringValue(Object text) {
      return ((TextWrapper)text).getReal().getString();
    }

    @Override
    public String getProcessingInstructionData(Object pi) {
      String s = ((ProcessingInstructionWrapper)pi).getReal().getData();
      int i = 0;
      while (i < s.length() && " \t\n\r".indexOf(s.charAt(i)) != -1) // strip initial whitespace
        i++;
      return s.substring(i);
    }

    @Override
    public String getProcessingInstructionTarget(Object pi) {
      return ((ProcessingInstructionWrapper)pi).getReal().getTarget();
    }

    @Override
    public Object getDocumentNode(Object node) {
      return root;
    }

    @Override
    public Iterator<?> getAttributeAxisIterator(Object node) {
      if (node instanceof ElementWrapper) {
        final ElementWrapper e = (ElementWrapper)node;
        return new Iterator<AttrNodeWrapper<? extends AttrNode>>() {
         
          AttrNodeWrapper<? extends AttrNode> next = e.getFirstAttribute();

          public boolean hasNext() {
            return next != null;
          }

          public AttrNodeWrapper<? extends AttrNode> next() {
            if (next == null)
              throw new NoSuchElementException();
            AttrNodeWrapper<? extends AttrNode> a = next;
            next = next.getNextAttribute();
            return a;
          }

          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      } else
        return JaxenConstants.EMPTY_ITERATOR;
    }

    @Override
    public Iterator<?> getChildAxisIterator(Object node) {
      if (node instanceof ElementWrapper) {
        final ElementWrapper e = (ElementWrapper)node;
        return new Iterator<TempNodeWrapper<? extends TempNode>>() {
         
          TempNodeWrapper<? extends TempNode> next = e.getFirstChild();

          public boolean hasNext() {
            return next != null;
          }

          public TempNodeWrapper<? extends TempNode> next() {
            if (next == null)
              throw new NoSuchElementException();
            TempNodeWrapper<? extends TempNode> x = next;
            next = next.getNextSibling();
            return x;
          }

          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      } else
        return JaxenConstants.EMPTY_ITERATOR;
    }

    @Override
    public Iterator<?> getFollowingSiblingAxisIterator(Object node) {
      if (node instanceof TempNodeWrapper<?>) {
        final TempNodeWrapper<? extends TempNode> t = (TempNodeWrapper<?>)node;
        return new Iterator<TempNodeWrapper<? extends TempNode>>() {
         
          TempNodeWrapper<? extends TempNode> next = t.getNextSibling();

          public boolean hasNext() {
            return next != null;
          }

          public TempNodeWrapper<? extends TempNode> next() {
            if (next == null)
              throw new NoSuchElementException();
            TempNodeWrapper<? extends TempNode> x = next;
            next = next.getNextSibling();
            return x;
          }

          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      } else
        return JaxenConstants.EMPTY_ITERATOR;
    }

    @Override
    public Object getElementById(Object node, String elementId) {
      return super.getElementById(node, elementId); // TODO: support getElementById (assume that ID attributes are named 'id')
      // build map for the entire template, cache at the original root node
    }

    @Override
    public Iterator<?> getAncestorAxisIterator(Object node) throws UnsupportedAxisException {
      throw new UnsupportedAxisException("ancestor"); // TODO: support (certain) backward axes?
    }

    @Override
    public Iterator<?> getAncestorOrSelfAxisIterator(Object node) throws UnsupportedAxisException {
      throw new UnsupportedAxisException("ancestor-or-self");
    }

    @Override
    public Iterator<?> getNamespaceAxisIterator(Object node) throws UnsupportedAxisException {
      throw new UnsupportedAxisException("namespace");
    }

    @Override
    public Iterator<?> getParentAxisIterator(Object node) {
      final ElementWrapper parent = ((NodeWrapper<?>)node).getParent();
      if (parent != null) {
        return Collections.singletonList(parent).iterator();
      } else
        return JaxenConstants.EMPTY_ITERATOR;
    }

    @Override
    public Object getParentNode(Object node) { // required by jaxen to sort results
      return ((NodeWrapper<?>)node).getParent();
    }

    @Override
    public Iterator<?> getPrecedingAxisIterator(Object node) throws UnsupportedAxisException {
      throw new UnsupportedAxisException("preceding");
    }

    @Override
    public Iterator<?> getPrecedingSiblingAxisIterator(Object node) throws UnsupportedAxisException {
      throw new UnsupportedAxisException("preceding-siblings");
    }
  }
 
  /**
   * Copies the wrapped nodes reachable from the root.
   * @param root root wrapper
   * @param selected selected node wrappers
   * @param remove_successors if true, the fresh selected nodes are detached from their successors
   * @return fresh selected nodes (the fresh root is at root.real)
   */
  private static List<Node> copyNodes(NodeWrapper<? extends TempNode> root, List<?> selected, boolean remove_successors) {
    final Set<?> selectedset = new HashSet<Object>(selected);
    final Stack<Entry> stack = new Stack<Entry>(); // use heap stack
    final LinkedList<Node> fresh_selected = new LinkedList<Node>();
    stack.push(new Entry(Entry.Kind.START_NODE, root));
    while (!stack.isEmpty()) {
      Entry e = stack.pop();
      switch (e.kind) {
      case START_NODE:
        stack.push(new Entry(Entry.Kind.END_NODE, e.node));
        e.node.visitBy(new WrapperNodeVisitor() {
          @Override public void visit(ElementWrapper w) {
            if (w.getFirstAttrWrapper() != null)
              stack.push(new Entry(Entry.Kind.START_NODE, w.getFirstAttrWrapper()));
            if (w.getFirstChildWrapper() != null)
              stack.push(new Entry(Entry.Kind.START_NODE, w.getFirstChildWrapper()));
          }
        });
        e.node.visitBy(new WrapperNodeVisitor() {
          @Override public void visit(AttrNodeWrapper<? extends AttrNode> w) {
            if (w.getNextAttrWrapper() != null)
              stack.push(new Entry(Entry.Kind.START_NODE, w.getNextAttrWrapper()));
          }
          @Override public void visit(TempNodeWrapper<? extends TempNode> w) {
            if (w.getNextSiblingWrapper() != null)
              stack.push(new Entry(Entry.Kind.START_NODE, w.getNextSiblingWrapper()));
          }
        });
        break;
      case END_NODE:
        e.node.visitBy(new WrapperNodeVisitor() {
          @Override public void visit(AttributeGapWrapper w) {
            w.setReal(w.getReal().copy(w.getRealNextAttr()));
          }
          @Override public void visit(AttributeWrapper w) {
            w.setReal(w.getReal().copy(w.getRealNextAttr()));
          }
          @Override public void visit(ElementWrapper w) {
            w.setReal(w.getReal().copy(w.getRealFirstAttr(), w.getRealFirstChild(), w.getRealNextSibling()));
          }
          @Override public void visit(TemplateGapWrapper w) {
            w.setReal(w.getReal().copy(w.getRealNextSibling()));
          }
          @Override public void visit(TextWrapper w) {
            w.setReal(w.getReal().copy(w.getRealNextSibling()));
          }
          @Override public void visit(CommentWrapper w) {
            w.setReal(w.getReal().copy(w.getRealNextSibling()));
          }
          @Override public void visit(ProcessingInstructionWrapper w) {
            w.setReal(w.getReal().copy(w.getRealNextSibling()));
          }
        });
        if (selectedset.contains(e.node) && e.node != root) {
          Node en = e.node.getReal();
          if (remove_successors) {
            if (en instanceof TempNode) {
              en = ((TempNode) en).copy(null);
            } else if (en instanceof AttrNode) {
              en = ((AttrNode) en).copy(null);
            }
          }
          fresh_selected.addFirst(en);
        }
        break;
      }
    }
    return fresh_selected;
  }

  static private class Entry {

    enum Kind {START_NODE, END_NODE};
   
    final Kind kind;
   
    final NodeWrapper<? extends Node> node;
   
    Entry(Kind kind, NodeWrapper<? extends Node> node) {
      this.kind = kind;
      this.node = node;
    }
  }
}
TOP

Related Classes of dk.brics.xact.operations.XMLNavigator$ElementListResult

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.