| /* Selector.java -- |
| Copyright (C) 2004,2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package gnu.xml.xpath; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| import javax.xml.XMLConstants; |
| import javax.xml.namespace.QName; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| |
| /** |
| * A single component of a location path. |
| * |
| * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> |
| */ |
| public final class Selector |
| extends Path |
| { |
| |
| public static final int ANCESTOR = 0; |
| public static final int ANCESTOR_OR_SELF = 1; |
| public static final int ATTRIBUTE = 2; |
| public static final int CHILD = 3; |
| public static final int DESCENDANT = 4; |
| public static final int DESCENDANT_OR_SELF = 5; |
| public static final int FOLLOWING = 6; |
| public static final int FOLLOWING_SIBLING = 7; |
| public static final int NAMESPACE = 8; |
| public static final int PARENT = 9; |
| public static final int PRECEDING = 10; |
| public static final int PRECEDING_SIBLING = 11; |
| public static final int SELF = 12; |
| |
| /** |
| * Axis to select nodes in. |
| */ |
| final int axis; |
| |
| /** |
| * List of tests to perform on candidates. |
| */ |
| final Test[] tests; |
| |
| public Selector(int axis, List tests) |
| { |
| this.axis = axis; |
| int len = tests.size(); |
| this.tests = new Test[(len == 0) ? 1 : len]; |
| if (len > 0) |
| tests.toArray(this.tests); |
| else |
| this.tests[0] = new NodeTypeTest((short) 0); |
| if (axis == NAMESPACE && this.tests[0] instanceof NameTest) |
| { |
| NameTest nt = (NameTest) this.tests[0]; |
| this.tests[0] = new NamespaceTest(nt.qName, nt.anyLocalName, nt.any); |
| } |
| } |
| |
| /** |
| * Returns the list of tests to perform on candidates. |
| */ |
| public Test[] getTests() |
| { |
| return tests; |
| } |
| |
| public boolean matches(Node context) |
| { |
| // If called directly, selector is the top level of the path |
| return matches(context, |
| getContextPosition(context), |
| getContextSize(context)); |
| } |
| |
| boolean matches(Node context, int pos, int len) |
| { |
| short nodeType = context.getNodeType(); |
| switch (axis) |
| { |
| case CHILD: |
| if (nodeType == Node.ATTRIBUTE_NODE) |
| return false; |
| break; |
| case ATTRIBUTE: |
| case NAMESPACE: |
| if (nodeType != Node.ATTRIBUTE_NODE) |
| return false; |
| break; |
| case DESCENDANT_OR_SELF: |
| return true; |
| default: |
| return false; |
| } |
| for (int j = 0; j < tests.length && len > 0; j++) |
| { |
| Test test = tests[j]; |
| if (!test.matches(context, pos, len)) |
| return false; |
| } |
| return true; |
| } |
| |
| private int getContextPosition(Node ctx) |
| { |
| int pos = 1; |
| for (ctx = ctx.getPreviousSibling(); ctx != null; |
| ctx = ctx.getPreviousSibling()) |
| { |
| if (tests[0].matches(ctx, 1, 1)) |
| pos++; |
| } |
| return pos; |
| } |
| |
| private int getContextSize(Node ctx) |
| { |
| if (ctx.getNodeType() == Node.ATTRIBUTE_NODE) |
| { |
| Node owner = ((Attr) ctx).getOwnerElement(); |
| return owner.getAttributes().getLength(); |
| } |
| int count = 1; |
| Node sib = ctx.getPreviousSibling(); |
| for (; sib != null; sib = sib.getPreviousSibling()) |
| { |
| if (tests[0].matches(ctx, 1, 1)) |
| count++; |
| } |
| sib = ctx.getNextSibling(); |
| for (; sib != null; sib = sib.getNextSibling()) |
| { |
| if (tests[0].matches(ctx, 1, 1)) |
| count++; |
| } |
| return count; |
| } |
| |
| public Object evaluate(Node context, int pos, int len) |
| { |
| Set acc = new LinkedHashSet(); |
| addCandidates(context, acc); |
| List candidates = new ArrayList(acc); |
| List ret = filterCandidates(candidates, false); |
| return ret; |
| } |
| |
| Collection evaluate(Node context, Collection ns) |
| { |
| Set acc = new LinkedHashSet(); |
| for (Iterator i = ns.iterator(); i.hasNext(); ) |
| addCandidates((Node) i.next(), acc); |
| List candidates = new ArrayList(acc); |
| List ret = filterCandidates(candidates, true); |
| return ret; |
| } |
| |
| /** |
| * Filter the given list of candidates according to the node tests. |
| */ |
| List filterCandidates(List candidates, boolean cascade) |
| { |
| int len = candidates.size(); |
| int tlen = tests.length; |
| if (tlen > 0 && len > 0) |
| { |
| // Present the result of each successful generation to the next test |
| for (int j = 0; j < tlen && len > 0; j++) |
| { |
| Test test = tests[j]; |
| List successful = new ArrayList(len); |
| for (int i = 0; i < len; i++) |
| { |
| Node node = (Node) candidates.get(i); |
| if (cascade) |
| { |
| // Documents and DocumentFragments should be considered |
| // if part of a location path where the axis involves |
| // the SELF concept |
| short nodeType = node.getNodeType(); |
| if ((nodeType == Node.DOCUMENT_NODE || |
| nodeType == Node.DOCUMENT_FRAGMENT_NODE) && |
| (axis == DESCENDANT_OR_SELF || |
| axis == ANCESTOR_OR_SELF || |
| axis == SELF) && |
| (tests.length == 1 && |
| tests[0] instanceof NodeTypeTest && |
| ((NodeTypeTest) tests[0]).type == (short) 0)) |
| { |
| successful.add(node); |
| continue; |
| } |
| } |
| if (test.matches(node, i + 1, len)) |
| successful.add(node); |
| } |
| candidates = successful; |
| len = candidates.size(); |
| } |
| } |
| return candidates; |
| } |
| |
| void addCandidates(Node context, Collection candidates) |
| { |
| // Build list of candidates |
| switch (axis) |
| { |
| case CHILD: |
| addChildNodes(context, candidates, false); |
| break; |
| case DESCENDANT: |
| addChildNodes(context, candidates, true); |
| break; |
| case DESCENDANT_OR_SELF: |
| candidates.add (context); |
| addChildNodes(context, candidates, true); |
| break; |
| case PARENT: |
| addParentNode(context, candidates, false); |
| break; |
| case ANCESTOR: |
| addParentNode(context, candidates, true); |
| break; |
| case ANCESTOR_OR_SELF: |
| candidates.add(context); |
| addParentNode(context, candidates, true); |
| break; |
| case FOLLOWING_SIBLING: |
| addFollowingNodes(context, candidates, false); |
| break; |
| case PRECEDING_SIBLING: |
| addPrecedingNodes(context, candidates, false); |
| break; |
| case FOLLOWING: |
| addFollowingNodes(context, candidates, true); |
| break; |
| case PRECEDING: |
| addPrecedingNodes(context, candidates, true); |
| break; |
| case ATTRIBUTE: |
| addAttributes(context, candidates); |
| break; |
| case NAMESPACE: |
| addNamespaceAttributes(context, candidates); |
| break; |
| case SELF: |
| candidates.add(context); |
| break; |
| } |
| } |
| |
| void addChildNodes(Node context, Collection acc, boolean recurse) |
| { |
| Node child = context.getFirstChild(); |
| while (child != null) |
| { |
| acc.add(child); |
| if (recurse) |
| addChildNodes(child, acc, recurse); |
| child = child.getNextSibling(); |
| } |
| } |
| |
| void addParentNode(Node context, Collection acc, boolean recurse) |
| { |
| Node parent = (context.getNodeType() == Node.ATTRIBUTE_NODE) ? |
| ((Attr) context).getOwnerElement() : context.getParentNode(); |
| if (parent != null) |
| { |
| acc.add(parent); |
| if (recurse) |
| addParentNode(parent, acc, recurse); |
| } |
| } |
| |
| void addFollowingNodes(Node context, Collection acc, boolean recurse) |
| { |
| if (context != null && recurse) |
| addChildNodes(context, acc, true); |
| Node cur = (context.getNodeType() == Node.ATTRIBUTE_NODE) ? null : |
| context.getNextSibling(); |
| while (cur != null) |
| { |
| acc.add(cur); |
| if (recurse) |
| addChildNodes(cur, acc, true); |
| cur = cur.getNextSibling(); |
| } |
| if (recurse) |
| { |
| while (context != null) |
| { |
| context = (context.getNodeType() == Node.ATTRIBUTE_NODE) ? |
| ((Attr) context).getOwnerElement() : context.getParentNode(); |
| if (context != null) |
| { |
| cur = context.getNextSibling(); |
| while (cur != null) |
| { |
| acc.add(cur); |
| if (recurse) |
| addChildNodes(cur, acc, true); |
| cur = cur.getNextSibling(); |
| } |
| } |
| } |
| } |
| } |
| |
| void addPrecedingNodes(Node context, Collection acc, boolean recurse) |
| { |
| Node cur = (context.getNodeType() == Node.ATTRIBUTE_NODE) ? null : |
| context.getPreviousSibling(); |
| while (cur != null) |
| { |
| acc.add(cur); |
| if (recurse) |
| addChildNodes(cur, acc, true); |
| cur = cur.getPreviousSibling(); |
| } |
| if (recurse) |
| { |
| cur = context; |
| cur = (cur.getNodeType() == Node.ATTRIBUTE_NODE) ? |
| ((Attr) cur).getOwnerElement() : cur.getParentNode(); |
| if (cur != null) |
| addPrecedingNodes(cur, acc, recurse); |
| } |
| } |
| |
| void addAttributes(Node context, Collection acc) |
| { |
| NamedNodeMap attrs = context.getAttributes(); |
| if (attrs != null) |
| { |
| int attrLen = attrs.getLength(); |
| for (int i = 0; i < attrLen; i++) |
| { |
| Node attr = attrs.item(i); |
| if (!isNamespaceAttribute(attr)) |
| { |
| acc.add(attr); |
| } |
| } |
| } |
| } |
| |
| void addNamespaceAttributes(Node context, Collection acc) |
| { |
| NamedNodeMap attrs = context.getAttributes(); |
| if (attrs != null) |
| { |
| int attrLen = attrs.getLength(); |
| for (int i = 0; i < attrLen; i++) |
| { |
| Node attr = attrs.item(i); |
| if (isNamespaceAttribute(attr)) |
| acc.add(attr); |
| } |
| } |
| } |
| |
| final boolean isNamespaceAttribute(Node node) |
| { |
| String uri = node.getNamespaceURI(); |
| return (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri) || |
| XMLConstants.XMLNS_ATTRIBUTE.equals(node.getPrefix()) || |
| XMLConstants.XMLNS_ATTRIBUTE.equals(node.getNodeName())); |
| } |
| |
| public Expr clone(Object context) |
| { |
| int len = tests.length; |
| List tests2 = new ArrayList(len); |
| for (int i = 0; i < len; i++) |
| tests2.add(tests[i].clone(context)); |
| return new Selector(axis, tests2); |
| } |
| |
| public boolean references(QName var) |
| { |
| for (int i = 0; i < tests.length; i++) |
| { |
| if (tests[i].references(var)) |
| return true; |
| } |
| return false; |
| } |
| |
| public String toString() |
| { |
| StringBuffer buf = new StringBuffer(); |
| switch (axis) |
| { |
| case ANCESTOR: |
| buf.append("ancestor::"); |
| break; |
| case ANCESTOR_OR_SELF: |
| buf.append("ancestor-or-self::"); |
| break; |
| case ATTRIBUTE: |
| if (tests.length == 0 || |
| (tests[0] instanceof NameTest)) |
| buf.append('@'); |
| else |
| buf.append("attribute::"); |
| break; |
| case CHILD: |
| //buf.append("child::"); |
| break; |
| case DESCENDANT: |
| buf.append("descendant::"); |
| break; |
| case DESCENDANT_OR_SELF: |
| buf.append("descendant-or-self::"); |
| break; |
| case FOLLOWING: |
| buf.append("following::"); |
| break; |
| case FOLLOWING_SIBLING: |
| buf.append("following-sibling::"); |
| break; |
| case NAMESPACE: |
| buf.append("namespace::"); |
| break; |
| case PARENT: |
| if (tests.length == 0 || |
| (tests[0] instanceof NodeTypeTest && |
| ((NodeTypeTest) tests[0]).type == 0)) |
| return ".."; |
| buf.append("parent::"); |
| break; |
| case PRECEDING: |
| buf.append("preceding::"); |
| break; |
| case PRECEDING_SIBLING: |
| buf.append("preceding-sibling::"); |
| break; |
| case SELF: |
| if (tests.length == 0 || |
| (tests[0] instanceof NodeTypeTest && |
| ((NodeTypeTest) tests[0]).type == 0)) |
| return "."; |
| buf.append("self::"); |
| break; |
| } |
| if (tests.length == 0) |
| buf.append("[error]"); |
| else |
| { |
| for (int i = 0; i < tests.length; i++) |
| buf.append(tests[i]); |
| } |
| return buf.toString(); |
| } |
| |
| } |