0001: package net.sf.saxon.om;
0002:
0003: import net.sf.saxon.Controller;
0004: import net.sf.saxon.event.Receiver;
0005: import net.sf.saxon.expr.Expression;
0006: import net.sf.saxon.expr.ReversibleIterator;
0007: import net.sf.saxon.expr.XPathContext;
0008: import net.sf.saxon.functions.EscapeURI;
0009: import net.sf.saxon.pattern.*;
0010: import net.sf.saxon.style.StandardNames;
0011: import net.sf.saxon.trans.XPathException;
0012: import net.sf.saxon.type.Type;
0013: import net.sf.saxon.value.SequenceExtent;
0014: import net.sf.saxon.value.Whitespace;
0015:
0016: import java.net.URI;
0017: import java.net.URISyntaxException;
0018: import java.util.ArrayList;
0019: import java.util.List;
0020:
0021: /**
0022: * The Navigator class provides helper classes for navigating a tree, irrespective
0023: * of its implementation
0024: *
0025: * @author Michael H. Kay
0026: */
0027:
0028: public final class Navigator {
0029:
0030: // Class is never instantiated
0031: private Navigator() {
0032: }
0033:
0034: /**
0035: * Get the string value of an attribute of a given element, given the URI and
0036: * local part of the attribute name.
0037: * @return the attribute value, or null if the attribute is not present
0038: */
0039:
0040: public static String getAttributeValue(NodeInfo element,
0041: String uri, String localName) {
0042: int fingerprint = element.getNamePool().allocate("", uri,
0043: localName);
0044: return element.getAttributeValue(fingerprint);
0045: }
0046:
0047: /**
0048: * Helper method to get the base URI of an element node
0049: */
0050:
0051: public static String getBaseURI(NodeInfo element) {
0052: String xmlBase = element
0053: .getAttributeValue(StandardNames.XML_BASE);
0054: if (xmlBase != null) {
0055: String escaped = EscapeURI.escape(xmlBase, false)
0056: .toString();
0057: URI escapedURI;
0058: try {
0059: escapedURI = new URI(escaped);
0060: if (!escapedURI.isAbsolute()) {
0061: NodeInfo parent = element.getParent();
0062: if (parent == null) {
0063: return escapedURI.toString();
0064: }
0065: String startSystemId = element.getSystemId();
0066: String parentSystemId = parent.getSystemId();
0067: if (startSystemId.equals(parentSystemId)) {
0068: URI base = new URI(element.getParent()
0069: .getBaseURI());
0070: escapedURI = base.resolve(escapedURI);
0071: } else {
0072: URI base = new URI(startSystemId);
0073: escapedURI = base.resolve(escapedURI);
0074: }
0075: }
0076: } catch (URISyntaxException e) {
0077: // xml:base is an invalid URI. Just return it as is: the operation that needs the base URI
0078: // will probably fail as a result.
0079: return xmlBase;
0080: }
0081: return escapedURI.toString();
0082: }
0083: String startSystemId = element.getSystemId();
0084: NodeInfo parent = element.getParent();
0085: if (parent == null) {
0086: return startSystemId;
0087: }
0088: String parentSystemId = parent.getSystemId();
0089: if (startSystemId.equals(parentSystemId)) {
0090: return parent.getBaseURI();
0091: } else {
0092: return startSystemId;
0093: }
0094: }
0095:
0096: /**
0097: * Output all namespace nodes associated with this element. Does nothing if
0098: * the node is not an element. This is a helper method to allow the method
0099: * {@link NodeInfo#sendNamespaceDeclarations(net.sf.saxon.event.Receiver, boolean)} to be
0100: * implemented if {@link NodeInfo#getDeclaredNamespaces(int[])} is available.
0101: * @param out The relevant outputter
0102: * @param includeAncestors True if namespaces declared on ancestor elements must be output
0103: */
0104:
0105: public static void sendNamespaceDeclarations(NodeInfo node,
0106: Receiver out, boolean includeAncestors)
0107: throws XPathException {
0108: if (node.getNodeKind() == Type.ELEMENT) {
0109: int[] codes;
0110: if (includeAncestors) {
0111: codes = new NamespaceIterator(node, null)
0112: .getInScopeNamespaceCodes();
0113: } else {
0114: codes = node
0115: .getDeclaredNamespaces(NodeInfo.EMPTY_NAMESPACE_LIST);
0116: }
0117: for (int i = 0; i < codes.length; i++) {
0118: if (codes[i] == -1)
0119: break;
0120: out.namespace(codes[i], 0);
0121: }
0122: }
0123: }
0124:
0125: /**
0126: * Get an absolute XPath expression that identifies a given node within its document
0127: *
0128: * @param node the node whose path is required. If null is supplied,
0129: * an empty string is returned - this fact is used in making a recursive call
0130: * for a parentless node.
0131: * @return a path expression that can be used to retrieve the node
0132: */
0133:
0134: public static String getPath(NodeInfo node) {
0135: if (node == null) {
0136: return "";
0137: }
0138: String pre;
0139: NodeInfo parent = node.getParent();
0140: // System.err.println("node = " + node + " parent = " + parent);
0141:
0142: switch (node.getNodeKind()) {
0143: case Type.DOCUMENT:
0144: return "/";
0145: case Type.ELEMENT:
0146: if (parent == null) {
0147: return node.getDisplayName();
0148: } else {
0149: pre = getPath(parent);
0150: if (pre.equals("/")) {
0151: return '/' + node.getDisplayName();
0152: } else {
0153: return pre + '/' + node.getDisplayName() + '['
0154: + getNumberSimple(node) + ']';
0155: }
0156: }
0157: case Type.ATTRIBUTE:
0158: return getPath(parent) + "/@" + node.getDisplayName();
0159: case Type.TEXT:
0160: pre = getPath(parent);
0161: return (pre.equals("/") ? "" : pre) + "/text()["
0162: + getNumberSimple(node) + ']';
0163: case Type.COMMENT:
0164: pre = getPath(parent);
0165: return (pre.equals("/") ? "" : pre) + "/comment()["
0166: + getNumberSimple(node) + ']';
0167: case Type.PROCESSING_INSTRUCTION:
0168: pre = getPath(parent);
0169: return (pre.equals("/") ? "" : pre)
0170: + "/processing-instruction()["
0171: + getNumberSimple(node) + ']';
0172: case Type.NAMESPACE:
0173: String test = node.getLocalPart();
0174: if (test.equals("")) {
0175: // default namespace: need a node-test that selects unnamed nodes only
0176: test = "*[not(local-name()]";
0177: }
0178: return getPath(parent) + "/namespace::" + test;
0179: default:
0180: return "";
0181: }
0182: }
0183:
0184: /**
0185: * Get simple node number. This is defined as one plus the number of previous siblings of the
0186: * same node type and name. It is not accessible directly in XSL.
0187: *
0188: * @param node The node whose number is required
0189: * @param context Used for remembering previous result, for
0190: * performance
0191: * @exception XPathException if any error occurs
0192: * @return the node number, as defined above
0193: */
0194:
0195: public static int getNumberSimple(NodeInfo node,
0196: XPathContext context) throws XPathException {
0197:
0198: //checkNumberable(node);
0199:
0200: int fingerprint = node.getFingerprint();
0201: NodeTest same;
0202:
0203: if (fingerprint == -1) {
0204: same = NodeKindTest.makeNodeKindTest(node.getNodeKind());
0205: } else {
0206: same = new NameTest(node);
0207: }
0208:
0209: SequenceIterator preceding = node.iterateAxis(
0210: Axis.PRECEDING_SIBLING, same);
0211:
0212: int i = 1;
0213: while (true) {
0214: NodeInfo prev = (NodeInfo) preceding.next();
0215: if (prev == null) {
0216: break;
0217: }
0218:
0219: Controller controller = context.getController();
0220: int memo = controller.getRememberedNumber(prev);
0221: if (memo > 0) {
0222: memo += i;
0223: controller.setRememberedNumber(node, memo);
0224: return memo;
0225: }
0226:
0227: i++;
0228: }
0229:
0230: context.getController().setRememberedNumber(node, i);
0231: return i;
0232: }
0233:
0234: /**
0235: * Get simple node number. This is defined as one plus the number of previous siblings of the
0236: * same node type and name. It is not accessible directly in XSL. This version doesn't require
0237: * the controller, and therefore doesn't remember previous results. It is used only by getPath().
0238: *
0239: * @param node the node whose number is required
0240: * @return the node number, as defined above
0241: */
0242:
0243: private static int getNumberSimple(NodeInfo node) {
0244:
0245: int fingerprint = node.getFingerprint();
0246: NodeTest same;
0247:
0248: if (fingerprint == -1) {
0249: same = NodeKindTest.makeNodeKindTest(node.getNodeKind());
0250: } else {
0251: same = new NameTest(node);
0252: }
0253:
0254: AxisIterator preceding = node.iterateAxis(
0255: Axis.PRECEDING_SIBLING, same);
0256:
0257: int i = 1;
0258: while (preceding.next() != null) {
0259: i++;
0260: }
0261:
0262: return i;
0263: }
0264:
0265: /**
0266: * Get node number (level="single"). If the current node matches the supplied pattern, the returned
0267: * number is one plus the number of previous siblings that match the pattern. Otherwise,
0268: * return the element number of the nearest ancestor that matches the supplied pattern.
0269: *
0270: * @param node the current node, the one whose node number is required
0271: * @param count Pattern that identifies which nodes should be
0272: * counted. Default (null) is the element name if the current node is
0273: * an element, or "node()" otherwise.
0274: * @param from Pattern that specifies where counting starts from.
0275: * Default (null) is the root node. (This parameter does not seem
0276: * useful but is included for the sake of XSLT conformance.)
0277: * @param context the dynamic context of the transformation, used if
0278: * the patterns reference context values (e.g. variables)
0279: * @exception XPathException when any error occurs in processing
0280: * @return the node number established as follows: go to the nearest
0281: * ancestor-or-self that matches the 'count' pattern and that is a
0282: * descendant of the nearest ancestor that matches the 'from' pattern.
0283: * Return one plus the nunber of preceding siblings of that ancestor
0284: * that match the 'count' pattern. If there is no such ancestor,
0285: * return 0.
0286: */
0287:
0288: public static int getNumberSingle(NodeInfo node, Pattern count,
0289: Pattern from, XPathContext context) throws XPathException {
0290:
0291: if (count == null && from == null) {
0292: return getNumberSimple(node, context);
0293: }
0294:
0295: boolean knownToMatch = false;
0296: if (count == null) {
0297: if (node.getFingerprint() == -1) { // unnamed node
0298: count = new NodeTestPattern(NodeKindTest
0299: .makeNodeKindTest(node.getNodeKind()));
0300: } else {
0301: count = new NodeTestPattern(new NameTest(node));
0302: }
0303: knownToMatch = true;
0304: }
0305:
0306: NodeInfo target = node;
0307: while (!(knownToMatch || count.matches(target, context))) {
0308: target = target.getParent();
0309: if (target == null) {
0310: return 0;
0311: }
0312: if (from != null && from.matches(target, context)) {
0313: return 0;
0314: }
0315: }
0316:
0317: // we've found the ancestor to count from
0318:
0319: SequenceIterator preceding = target.iterateAxis(
0320: Axis.PRECEDING_SIBLING, count.getNodeTest());
0321: // pass the filter condition down to the axis enumeration where possible
0322: boolean alreadyChecked = (count instanceof NodeTestPattern);
0323: int i = 1;
0324: while (true) {
0325: NodeInfo p = (NodeInfo) preceding.next();
0326: if (p == null) {
0327: return i;
0328: }
0329: if (alreadyChecked || count.matches(p, context)) {
0330: i++;
0331: }
0332: }
0333: }
0334:
0335: /**
0336: * Get node number (level="any").
0337: * Return one plus the number of previous nodes in the
0338: * document that match the supplied pattern
0339: *
0340: * @exception net.sf.saxon.trans.XPathException
0341: * @param inst Identifies the xsl:number expression; this is relevant
0342: * when the function is memoised to support repeated use of the same
0343: * instruction to number multiple nodes
0344: * @param node The node being numbered
0345: * @param count Pattern that identifies which nodes should be
0346: * counted. Default (null) is the element name if the current node is
0347: * an element, or "node()" otherwise.
0348: * @param from Pattern that specifies where counting starts from.
0349: * Default (null) is the root node. Only nodes after the first (most
0350: * recent) node that matches the 'from' pattern are counted.
0351: * @param context The dynamic context for the transformation
0352: * @param hasVariablesInPatterns if the count or from patterns
0353: * contain variables, then it's not safe to get the answer by adding
0354: * one to the number of the most recent node that matches
0355: * @return one plus the number of nodes that precede the current node,
0356: * that match the count pattern, and that follow the first node that
0357: * matches the from pattern if specified.
0358: */
0359:
0360: public static int getNumberAny(Expression inst, NodeInfo node,
0361: Pattern count, Pattern from, XPathContext context,
0362: boolean hasVariablesInPatterns) throws XPathException {
0363:
0364: NodeInfo memoNode = null;
0365: int memoNumber = 0;
0366: Controller controller = context.getController();
0367: boolean memoise = (!hasVariablesInPatterns && count != null);
0368: if (memoise) {
0369: Object[] memo = (Object[]) controller.getUserData(inst,
0370: "xsl:number");
0371: if (memo != null) {
0372: memoNode = (NodeInfo) memo[0];
0373: memoNumber = ((Integer) memo[1]).intValue();
0374: }
0375: }
0376:
0377: int num = 0;
0378: if (count == null) {
0379: if (node.getFingerprint() == -1) { // unnamed node
0380: count = new NodeTestPattern(NodeKindTest
0381: .makeNodeKindTest(node.getNodeKind()));
0382: } else {
0383: count = new NodeTestPattern(new NameTest(node));
0384: }
0385: num = 1;
0386: } else if (count.matches(node, context)) {
0387: num = 1;
0388: }
0389:
0390: // We use a special axis invented for the purpose: the union of the preceding and
0391: // ancestor axes, but in reverse document order
0392:
0393: // Pass part of the filtering down to the axis iterator if possible
0394: NodeTest filter;
0395: if (from == null) {
0396: filter = count.getNodeTest();
0397: } else if (from.getNodeKind() == Type.ELEMENT
0398: && count.getNodeKind() == Type.ELEMENT) {
0399: filter = NodeKindTest.ELEMENT;
0400: } else {
0401: filter = AnyNodeTest.getInstance();
0402: }
0403:
0404: SequenceIterator preceding = node.iterateAxis(
0405: Axis.PRECEDING_OR_ANCESTOR, filter);
0406:
0407: while (true) {
0408: NodeInfo prev = (NodeInfo) preceding.next();
0409: if (prev == null) {
0410: break;
0411: }
0412: if (from != null && from.matches(prev, context)) {
0413: return num;
0414: }
0415: if (count.matches(prev, context)) {
0416: if (num == 1 && memoNode != null
0417: && prev.isSameNodeInfo(memoNode)) {
0418: num = memoNumber + 1;
0419: break;
0420: }
0421: num++;
0422: }
0423: }
0424: if (from != null) {
0425: // we've got to the end without matching the from pattern - result is ()
0426: return 0;
0427: }
0428: if (memoise) {
0429: Object[] memo = new Object[2];
0430: memo[0] = node;
0431: memo[1] = new Integer(num);
0432: controller.setUserData(inst, "xsl:number", memo);
0433: }
0434: return num;
0435: }
0436:
0437: /**
0438: * Get node number (level="multiple").
0439: * Return a vector giving the hierarchic position of this node. See the XSLT spec for details.
0440: *
0441: * @exception XPathException
0442: * @param node The node to be numbered
0443: * @param count Pattern that identifies which nodes (ancestors and
0444: * their previous siblings) should be counted. Default (null) is the
0445: * element name if the current node is an element, or "node()"
0446: * otherwise.
0447: * @param from Pattern that specifies where counting starts from.
0448: * Default (null) is the root node. Only nodes below the first (most
0449: * recent) node that matches the 'from' pattern are counted.
0450: * @param context The dynamic context for the transformation
0451: * @return a vector containing for each ancestor-or-self that matches the
0452: * count pattern and that is below the nearest node that matches the
0453: * from pattern, an Integer which is one greater than the number of
0454: * previous siblings that match the count pattern.
0455: */
0456:
0457: public static List getNumberMulti(NodeInfo node, Pattern count,
0458: Pattern from, XPathContext context) throws XPathException {
0459:
0460: //checkNumberable(node);
0461:
0462: ArrayList v = new ArrayList(5);
0463:
0464: if (count == null) {
0465: if (node.getFingerprint() == -1) { // unnamed node
0466: count = new NodeTestPattern(NodeKindTest
0467: .makeNodeKindTest(node.getNodeKind()));
0468: } else {
0469: count = new NodeTestPattern(new NameTest(node));
0470: }
0471: }
0472:
0473: NodeInfo curr = node;
0474:
0475: while (true) {
0476: if (count.matches(curr, context)) {
0477: int num = getNumberSingle(curr, count, null, context);
0478: v.add(0, new Long(num));
0479: }
0480: curr = curr.getParent();
0481: if (curr == null)
0482: break;
0483: if (from != null && from.matches(curr, context))
0484: break;
0485: }
0486:
0487: return v;
0488: }
0489:
0490: /**
0491: * Generic (model-independent) implementation of deep copy algorithm for nodes.
0492: * This is available for use by any node implementations that choose to use it.
0493: * @param node The node to be copied
0494: * @param out The receiver to which events will be sent
0495: * @param namePool Namepool holding the name codes (used only to resolve namespace
0496: * codes)
0497: * @param whichNamespaces Indicates which namespace nodes for an element should
0498: * be copied
0499: * @param copyAnnotations Indicates whether type annotations should be copied
0500: * @throws XPathException on any failure reported by the Receiver
0501: */
0502:
0503: public static void copy(NodeInfo node, Receiver out,
0504: NamePool namePool, int whichNamespaces,
0505: boolean copyAnnotations, int locationId)
0506: throws XPathException {
0507:
0508: switch (node.getNodeKind()) {
0509: case Type.DOCUMENT: {
0510: out.startDocument(0);
0511: AxisIterator children0 = node.iterateAxis(Axis.CHILD,
0512: new AnyNodeTest());
0513: while (true) {
0514: NodeInfo child = (NodeInfo) children0.next();
0515: if (child == null) {
0516: break;
0517: }
0518: child.copy(out, whichNamespaces, copyAnnotations,
0519: locationId);
0520: }
0521: out.endDocument();
0522: break;
0523: }
0524: case Type.ELEMENT: {
0525: out.startElement(node.getNameCode(), copyAnnotations ? node
0526: .getTypeAnnotation() : StandardNames.XDT_UNTYPED,
0527: 0, 0);
0528:
0529: // output the namespaces
0530:
0531: if (whichNamespaces != NodeInfo.NO_NAMESPACES) {
0532: node.sendNamespaceDeclarations(out, true);
0533: }
0534:
0535: // output the attributes
0536:
0537: AxisIterator attributes = node.iterateAxis(Axis.ATTRIBUTE,
0538: new AnyNodeTest());
0539: while (true) {
0540: NodeInfo att = (NodeInfo) attributes.next();
0541: if (att == null) {
0542: break;
0543: }
0544: att.copy(out, whichNamespaces, copyAnnotations,
0545: locationId);
0546: }
0547:
0548: // notify the start of content
0549:
0550: out.startContent();
0551:
0552: // output the children
0553:
0554: AxisIterator children = node.iterateAxis(Axis.CHILD,
0555: new AnyNodeTest());
0556: while (true) {
0557: NodeInfo child = (NodeInfo) children.next();
0558: if (child == null) {
0559: break;
0560: }
0561: child.copy(out, whichNamespaces, copyAnnotations,
0562: locationId);
0563: }
0564:
0565: // finally the end tag
0566:
0567: out.endElement();
0568: return;
0569: }
0570: case Type.ATTRIBUTE: {
0571: out.attribute(node.getNameCode(), copyAnnotations ? node
0572: .getTypeAnnotation()
0573: : StandardNames.XDT_UNTYPED_ATOMIC, node
0574: .getStringValueCS(), 0, 0);
0575: return;
0576: }
0577: case Type.TEXT: {
0578: out.characters(node.getStringValueCS(), 0, 0);
0579: return;
0580: }
0581: case Type.COMMENT: {
0582: out.comment(node.getStringValueCS(), 0, 0);
0583: return;
0584: }
0585: case Type.PROCESSING_INSTRUCTION: {
0586: out.processingInstruction(node.getLocalPart(), node
0587: .getStringValueCS(), 0, 0);
0588: return;
0589: }
0590: case Type.NAMESPACE: {
0591: out.namespace(namePool.allocateNamespaceCode(node
0592: .getLocalPart(), node.getStringValue()), 0);
0593: return;
0594: }
0595: default:
0596:
0597: }
0598: }
0599:
0600: /**
0601: * Generic (model-independent) method to determine the relative position of two
0602: * node in document order. The nodes must be in the same tree.
0603: * @param first The first node
0604: * @param second The second node, whose position is to be compared with the first node
0605: * @return -1 if this node precedes the other node, +1 if it follows the other
0606: * node, or 0 if they are the same node. (In this case, isSameNode() will always
0607: * return true, and the two nodes will produce the same result for generateId())
0608: */
0609:
0610: public static int compareOrder(SiblingCountingNode first,
0611: SiblingCountingNode second) {
0612: NodeInfo ow = second;
0613:
0614: // are they the same node?
0615: if (first.isSameNodeInfo(second)) {
0616: return 0;
0617: }
0618:
0619: NodeInfo firstParent = first.getParent();
0620: if (firstParent == null) {
0621: // first node is the root
0622: return -1;
0623: }
0624:
0625: NodeInfo secondParent = second.getParent();
0626: if (secondParent == null) {
0627: // second node is the root
0628: return +1;
0629: }
0630:
0631: // do they have the same parent (common case)?
0632: if (firstParent.isSameNodeInfo(secondParent)) {
0633: int cat1 = nodeCategories[first.getNodeKind()];
0634: int cat2 = nodeCategories[second.getNodeKind()];
0635: if (cat1 == cat2) {
0636: return first.getSiblingPosition()
0637: - second.getSiblingPosition();
0638: } else {
0639: return cat1 - cat2;
0640: }
0641: }
0642:
0643: // find the depths of both nodes in the tree
0644: int depth1 = 0;
0645: int depth2 = 0;
0646: NodeInfo p1 = first;
0647: NodeInfo p2 = second;
0648: while (p1 != null) {
0649: depth1++;
0650: p1 = p1.getParent();
0651: }
0652: while (p2 != null) {
0653: depth2++;
0654: p2 = p2.getParent();
0655: }
0656: // move up one branch of the tree so we have two nodes on the same level
0657:
0658: p1 = first;
0659: while (depth1 > depth2) {
0660: p1 = p1.getParent();
0661: if (p1.isSameNodeInfo(second)) {
0662: return +1;
0663: }
0664: depth1--;
0665: }
0666:
0667: p2 = ow;
0668: while (depth2 > depth1) {
0669: p2 = p2.getParent();
0670: if (p2.isSameNodeInfo(first)) {
0671: return -1;
0672: }
0673: depth2--;
0674: }
0675:
0676: // now move up both branches in sync until we find a common parent
0677: while (true) {
0678: NodeInfo par1 = p1.getParent();
0679: NodeInfo par2 = p2.getParent();
0680: if (par1 == null || par2 == null) {
0681: throw new NullPointerException(
0682: "DOM/JDOM tree compare - internal error");
0683: }
0684: if (par1.isSameNodeInfo(par2)) {
0685: return ((SiblingCountingNode) p1).getSiblingPosition()
0686: - ((SiblingCountingNode) p2)
0687: .getSiblingPosition();
0688: }
0689: p1 = par1;
0690: p2 = par2;
0691: }
0692: }
0693:
0694: /**
0695: * Classify node kinds into categories for sorting into document order:
0696: * 0 = document, 1 = namespace, 2 = attribute, 3 = (element, text, comment, pi)
0697: */
0698:
0699: private static int[] nodeCategories = { -1, //0 = not used
0700: 3, //1 = element
0701: 2, //2 = attribute
0702: 3, //3 = text
0703: -1, -1, -1, //4,5,6 = not used
0704: 3, //7 = processing-instruction
0705: 3, //8 = comment
0706: 0, //9 = document
0707: -1, -1, -1, //10,11,12 = not used
0708: 1 //13 = namespace
0709: };
0710:
0711: /**
0712: * Get a character string that uniquely identifies this node and that collates nodes
0713: * into document order
0714: * @return a string. The string is always interned so keys can be compared using "==".
0715: */
0716:
0717: public static String getSequentialKey(SiblingCountingNode node) {
0718: // This was designed so it could be used for sorting nodes into document
0719: // order, but is not currently used that way.
0720: StringBuffer key = new StringBuffer(12);
0721: int num = node.getDocumentNumber();
0722: while (node != null && node.getNodeKind() != Type.DOCUMENT) {
0723: key.insert(0, alphaKey(node.getSiblingPosition()));
0724: node = (SiblingCountingNode) node.getParent();
0725: }
0726: key.insert(0, "w" + num);
0727: return key.toString().intern();
0728: }
0729:
0730: /**
0731: * Construct an alphabetic key from an positive integer; the key collates in the same sequence
0732: * as the integer
0733: * @param value The positive integer key value (negative values are treated as zero).
0734: */
0735:
0736: public static String alphaKey(int value) {
0737: if (value < 1)
0738: return "a";
0739: if (value < 10)
0740: return "b" + value;
0741: if (value < 100)
0742: return "c" + value;
0743: if (value < 1000)
0744: return "d" + value;
0745: if (value < 10000)
0746: return "e" + value;
0747: if (value < 100000)
0748: return "f" + value;
0749: if (value < 1000000)
0750: return "g" + value;
0751: if (value < 10000000)
0752: return "h" + value;
0753: if (value < 100000000)
0754: return "i" + value;
0755: if (value < 1000000000)
0756: return "j" + value;
0757: return "k" + value;
0758: }
0759:
0760: /**
0761: * Determine if a string is all-whitespace
0762: *
0763: * @param content the string to be tested
0764: * @return true if the supplied string contains no non-whitespace
0765: * characters
0766: * @deprecated since Saxon 8.5: use {@link Whitespace#isWhite}
0767: */
0768:
0769: public static final boolean isWhite(CharSequence content) {
0770: return Whitespace.isWhite(content);
0771: }
0772:
0773: ///////////////////////////////////////////////////////////////////////////////
0774: // Helper classes to support axis iteration
0775: ///////////////////////////////////////////////////////////////////////////////
0776:
0777: /**
0778: * AxisFilter is an iterator that applies a NodeTest filter to
0779: * the nodes returned by an underlying AxisIterator.
0780: */
0781:
0782: public static class AxisFilter extends AxisIteratorImpl {
0783: private AxisIterator base;
0784: private NodeTest nodeTest;
0785:
0786: //private int last = -1;
0787:
0788: /** S
0789: * Construct a AxisFilter
0790: * @param base the underlying iterator that returns all the nodes on
0791: * a required axis. This must not be an atomizing iterator!
0792: * @param test a NodeTest that is applied to each node returned by the
0793: * underlying AxisIterator; only those nodes that pass the NodeTest are
0794: * returned by the AxisFilter
0795: */
0796:
0797: public AxisFilter(AxisIterator base, NodeTest test) {
0798: this .base = base;
0799: this .nodeTest = test;
0800: position = 0;
0801: }
0802:
0803: public Item next() {
0804: while (true) {
0805: current = base.next();
0806: if (current == null) {
0807: position = -1;
0808: return null;
0809: }
0810: if (nodeTest.matches((NodeInfo) current)) {
0811: position++;
0812: return current;
0813: }
0814: }
0815: }
0816:
0817: public SequenceIterator getAnother() {
0818: return new AxisFilter((AxisIterator) base.getAnother(),
0819: nodeTest);
0820: }
0821: }
0822:
0823: /**
0824: * BaseEnumeration is an abstract implementation of an AxisIterator, it
0825: * simplifies the implementation of the underlying AxisIterator by requiring
0826: * it to provide only two methods: advance(), and getAnother().
0827: *
0828: * NOTA BENE: BaseEnumeration does not maintain the value of the position variable.
0829: * It must therefore either (a) be wrapped in an AxisFilter, which does maintain
0830: * position, or (b) be subclassed by a class that maintains position itself.
0831: */
0832:
0833: public static abstract class BaseEnumeration extends
0834: AxisIteratorImpl {
0835:
0836: public final Item next() {
0837: advance();
0838: return current;
0839: }
0840:
0841: /**
0842: * The advance() method must be provided in each concrete implementation.
0843: * It must leave the variable current set to the next node to be returned in the
0844: * iteration, or to null if there are no more nodes to be returned.
0845: */
0846:
0847: public abstract void advance();
0848:
0849: public abstract SequenceIterator getAnother();
0850:
0851: }
0852:
0853: /**
0854: * General-purpose implementation of the ancestor and ancestor-or-self axes
0855: */
0856:
0857: public static final class AncestorEnumeration extends
0858: BaseEnumeration {
0859:
0860: private boolean includeSelf;
0861: private boolean atStart;
0862: private NodeInfo start;
0863:
0864: public AncestorEnumeration(NodeInfo start, boolean includeSelf) {
0865: this .start = start;
0866: this .includeSelf = includeSelf;
0867: this .current = start;
0868: atStart = true;
0869: }
0870:
0871: public void advance() {
0872: if (atStart) {
0873: atStart = false;
0874: if (includeSelf) {
0875: return;
0876: }
0877: }
0878: current = ((NodeInfo) current).getParent();
0879: }
0880:
0881: public SequenceIterator getAnother() {
0882: return new AncestorEnumeration(start, includeSelf);
0883: }
0884:
0885: } // end of class AncestorEnumeration
0886:
0887: /**
0888: * General-purpose implementation of the descendant and descendant-or-self axes,
0889: * in terms of the child axis.
0890: * But it also has the option to return the descendants in reverse document order;
0891: * this is used when evaluating the preceding axis. Note that the includeSelf option
0892: * should not be used when scanning in reverse order, as the self node will always be
0893: * returned first.
0894: */
0895:
0896: public static final class DescendantEnumeration extends
0897: BaseEnumeration {
0898:
0899: private AxisIterator children = null;
0900: private AxisIterator descendants = null;
0901: private NodeInfo start;
0902: private boolean includeSelf;
0903: private boolean forwards;
0904: private boolean atEnd = false;
0905:
0906: public DescendantEnumeration(NodeInfo start,
0907: boolean includeSelf, boolean forwards) {
0908: this .start = start;
0909: this .includeSelf = includeSelf;
0910: this .forwards = forwards;
0911: }
0912:
0913: public void advance() {
0914: if (descendants != null) {
0915: Item nextd = descendants.next();
0916: if (nextd != null) {
0917: current = nextd;
0918: return;
0919: } else {
0920: descendants = null;
0921: }
0922: }
0923: if (children != null) {
0924: NodeInfo n = (NodeInfo) children.next();
0925: if (n != null) {
0926: if (n.hasChildNodes()) {
0927: if (forwards) {
0928: descendants = new DescendantEnumeration(n,
0929: false, forwards);
0930: current = n;
0931: } else {
0932: descendants = new DescendantEnumeration(n,
0933: true, forwards);
0934: advance();
0935: }
0936: } else {
0937: current = n;
0938: }
0939: } else {
0940: if (forwards || !includeSelf) {
0941: current = null;
0942: } else {
0943: atEnd = true;
0944: children = null;
0945: current = start;
0946: }
0947: }
0948: } else if (atEnd) {
0949: // we're just finishing a backwards scan
0950: current = null;
0951: } else {
0952: // we're just starting...
0953: if (start.hasChildNodes()) {
0954: //children = new NodeWrapper.ChildEnumeration(start, true, forwards);
0955: children = start.iterateAxis(Axis.CHILD);
0956: if (!forwards) {
0957: if (children instanceof ReversibleIterator) {
0958: children = (AxisIterator) ((ReversibleIterator) children)
0959: .getReverseIterator();
0960: } else {
0961: try {
0962: children = new SequenceExtent(start
0963: .iterateAxis(Axis.CHILD))
0964: .reverseIterate();
0965: } catch (XPathException e) {
0966: throw new AssertionError(
0967: "Internal error in Navigator#descendantEnumeration: "
0968: + e.getMessage());
0969: // shouldn't happen.
0970: }
0971: }
0972: }
0973: } else {
0974: children = EmptyIterator.getInstance();
0975: }
0976: if (forwards && includeSelf) {
0977: current = start;
0978: } else {
0979: advance();
0980: }
0981: }
0982: }
0983:
0984: public SequenceIterator getAnother() {
0985: return new DescendantEnumeration(start, includeSelf,
0986: forwards);
0987: }
0988:
0989: } // end of class DescendantEnumeration
0990:
0991: /**
0992: * General purpose implementation of the following axis, in terms of the
0993: * ancestor, child, and following-sibling axes
0994: */
0995:
0996: public static final class FollowingEnumeration extends
0997: BaseEnumeration {
0998: private NodeInfo start;
0999: private AxisIterator ancestorEnum = null;
1000: private AxisIterator siblingEnum = null;
1001: private AxisIterator descendEnum = null;
1002:
1003: public FollowingEnumeration(NodeInfo start) {
1004: this .start = start;
1005: ancestorEnum = new AncestorEnumeration(start, false);
1006: switch (start.getNodeKind()) {
1007: case Type.ELEMENT:
1008: case Type.TEXT:
1009: case Type.COMMENT:
1010: case Type.PROCESSING_INSTRUCTION:
1011: //siblingEnum = new NodeWrapper.ChildEnumeration(start, false, true);
1012: // gets following siblings
1013: siblingEnum = start.iterateAxis(Axis.FOLLOWING_SIBLING);
1014: break;
1015: case Type.ATTRIBUTE:
1016: case Type.NAMESPACE:
1017: //siblingEnum = new NodeWrapper.ChildEnumeration((NodeWrapper)start.getParent(), true, true);
1018: // gets children of the attribute's parent node
1019: NodeInfo parent = start.getParent();
1020: if (parent == null) {
1021: siblingEnum = EmptyIterator.getInstance();
1022: } else {
1023: siblingEnum = parent.iterateAxis(Axis.CHILD);
1024: }
1025: break;
1026: default:
1027: siblingEnum = EmptyIterator.getInstance();
1028: }
1029: //advance();
1030: }
1031:
1032: public void advance() {
1033: if (descendEnum != null) {
1034: Item nextd = descendEnum.next();
1035: if (nextd != null) {
1036: current = nextd;
1037: return;
1038: } else {
1039: descendEnum = null;
1040: }
1041: }
1042: if (siblingEnum != null) {
1043: Item nexts = siblingEnum.next();
1044: if (nexts != null) {
1045: current = nexts;
1046: NodeInfo n = (NodeInfo) current;
1047: if (n.hasChildNodes()) {
1048: descendEnum = new DescendantEnumeration(n,
1049: false, true);
1050: } else {
1051: descendEnum = null;
1052: }
1053: return;
1054: } else {
1055: descendEnum = null;
1056: siblingEnum = null;
1057: }
1058: }
1059: Item nexta = ancestorEnum.next();
1060: if (nexta != null) {
1061: current = nexta;
1062: NodeInfo n = (NodeInfo) current;
1063: if (n.getNodeKind() == Type.DOCUMENT) {
1064: siblingEnum = EmptyIterator.getInstance();
1065: } else {
1066: //siblingEnum = new NodeWrapper.ChildEnumeration(next, false, true);
1067: siblingEnum = n.iterateAxis(Axis.FOLLOWING_SIBLING);
1068: }
1069: advance();
1070: } else {
1071: current = null;
1072: }
1073: }
1074:
1075: public SequenceIterator getAnother() {
1076: return new FollowingEnumeration(start);
1077: }
1078:
1079: } // end of class FollowingEnumeration
1080:
1081: public static final class PrecedingEnumeration extends
1082: BaseEnumeration {
1083:
1084: private NodeInfo start;
1085: private AxisIterator ancestorEnum = null;
1086: private AxisIterator siblingEnum = null;
1087: private AxisIterator descendEnum = null;
1088: private boolean includeAncestors;
1089:
1090: public PrecedingEnumeration(NodeInfo start,
1091: boolean includeAncestors) {
1092: this .start = start;
1093: this .includeAncestors = includeAncestors;
1094: ancestorEnum = new AncestorEnumeration(start, false);
1095: switch (start.getNodeKind()) {
1096: case Type.ELEMENT:
1097: case Type.TEXT:
1098: case Type.COMMENT:
1099: case Type.PROCESSING_INSTRUCTION:
1100: // get preceding-sibling enumeration
1101: //siblingEnum = new NodeWrapper.ChildEnumeration(start, false, false);
1102: siblingEnum = start.iterateAxis(Axis.PRECEDING_SIBLING);
1103: break;
1104: default:
1105: siblingEnum = EmptyIterator.getInstance();
1106: }
1107: //advance();
1108: }
1109:
1110: public void advance() {
1111: if (descendEnum != null) {
1112: Item nextd = descendEnum.next();
1113: if (nextd != null) {
1114: current = nextd;
1115: return;
1116: } else {
1117: descendEnum = null;
1118: }
1119: }
1120: if (siblingEnum != null) {
1121: Item nexts = siblingEnum.next();
1122: if (nexts != null) {
1123: NodeInfo sib = (NodeInfo) nexts;
1124: if (sib.hasChildNodes()) {
1125: descendEnum = new DescendantEnumeration(sib,
1126: true, false);
1127: advance();
1128: } else {
1129: descendEnum = null;
1130: current = sib;
1131: }
1132: return;
1133: } else {
1134: descendEnum = null;
1135: siblingEnum = null;
1136: }
1137: }
1138: Item nexta = ancestorEnum.next();
1139: if (nexta != null) {
1140: current = nexta;
1141: NodeInfo n = (NodeInfo) current;
1142: if (n.getNodeKind() == Type.DOCUMENT) {
1143: siblingEnum = EmptyIterator.getInstance();
1144: } else {
1145: siblingEnum = n.iterateAxis(Axis.PRECEDING_SIBLING);
1146: }
1147: if (!includeAncestors) {
1148: advance();
1149: }
1150: } else {
1151: current = null;
1152: }
1153: }
1154:
1155: public SequenceIterator getAnother() {
1156: return new PrecedingEnumeration(start, includeAncestors);
1157: }
1158:
1159: } // end of class PrecedingEnumeration
1160:
1161: }
1162:
1163: //
1164: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
1165: // you may not use this file except in compliance with the License. You may obtain a copy of the
1166: // License at http://www.mozilla.org/MPL/
1167: //
1168: // Software distributed under the License is distributed on an "AS IS" basis,
1169: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
1170: // See the License for the specific language governing rights and limitations under the License.
1171: //
1172: // The Original Code is: all this file.
1173: //
1174: // The Initial Developer of the Original Code is Michael H. Kay.
1175: //
1176: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
1177: //
1178: // Contributor(s): none.
1179: //
|