001: package net.sf.saxon.functions;
002:
003: import net.sf.saxon.Controller;
004: import net.sf.saxon.type.Type;
005: import net.sf.saxon.expr.*;
006: import net.sf.saxon.om.*;
007: import net.sf.saxon.sort.DocumentOrderIterator;
008: import net.sf.saxon.sort.LocalOrderComparer;
009: import net.sf.saxon.style.ExpressionContext;
010: import net.sf.saxon.trans.KeyManager;
011: import net.sf.saxon.trans.StaticError;
012: import net.sf.saxon.trans.XPathException;
013: import net.sf.saxon.value.AtomicValue;
014: import net.sf.saxon.value.Cardinality;
015: import net.sf.saxon.value.StringValue;
016:
017: public class KeyFn extends SystemFunction implements XSLTFunction {
018:
019: private NamespaceResolver nsContext = null;
020: private int keyFingerprint = -1;
021: private transient boolean checked = false;
022: private transient boolean internal = false;
023:
024: // the second time checkArguments is called, it's a global check so the static context is inaccurate
025:
026: /**
027: * Non-standard constructor to create an internal call on key() with a known key fingerprint
028: */
029:
030: public static KeyFn internalKeyCall(NamePool pool, int fingerprint,
031: String name, Expression value, Expression doc) {
032: KeyFn k = new KeyFn();
033: Expression[] arguments = { new StringValue(name), value, doc };
034: k.argument = arguments;
035: k.keyFingerprint = fingerprint;
036: k.checked = true;
037: k.internal = true;
038: k.setDetails(StandardFunction.getFunction("key", 3));
039: k.setFunctionNameCode(pool.allocate("fn", NamespaceConstant.FN,
040: "key"));
041: return k;
042: }
043:
044: /**
045: * Simplify: add a third implicit argument, the context document
046: */
047:
048: public Expression simplify(StaticContext env) throws XPathException {
049: if (!internal && !(env instanceof ExpressionContext)) {
050: throw new StaticError(
051: "The key() function is available only in XPath expressions within an XSLT stylesheet");
052: }
053: KeyFn f = (KeyFn) super .simplify(env);
054: if (argument.length == 2) {
055: f.addContextDocumentArgument(2, "key");
056: }
057: return f;
058: }
059:
060: public void checkArguments(StaticContext env) throws XPathException {
061: if (checked)
062: return;
063: checked = true;
064: super .checkArguments(env);
065: Optimizer opt = env.getConfiguration().getOptimizer();
066: argument[1] = ExpressionTool.unsorted(opt, argument[1], false);
067: if (argument[0] instanceof StringValue) {
068: // common case, key name is supplied as a constant
069: try {
070: keyFingerprint = ((ExpressionContext) env)
071: .getFingerprint(((StringValue) argument[0])
072: .getStringValue(), false);
073: } catch (XPathException e) {
074: StaticError err = new StaticError("Error in key name "
075: + ((StringValue) argument[0]).getStringValue()
076: + ": " + e.getMessage());
077: err.setLocator(this );
078: err.setErrorCode("XTDE1260");
079: throw err;
080: }
081: if (keyFingerprint == -1) {
082: StaticError err = new StaticError("Key "
083: + ((StringValue) argument[0]).getStringValue()
084: + " has not been defined");
085: err.setLocator(this );
086: err.setErrorCode("XTDE1260");
087: throw err;
088: }
089: } else {
090: // we need to save the namespace context
091: nsContext = env.getNamespaceResolver();
092: }
093: }
094:
095: /**
096: * Get the static properties of this expression (other than its type). The result is
097: * bit-signficant. These properties are used for optimizations. In general, if
098: * a property bit is set, it is true, but if it is unset, the value is unknown.
099: */
100:
101: public int computeSpecialProperties() {
102: int prop = StaticProperty.ORDERED_NODESET
103: | StaticProperty.SINGLE_DOCUMENT_NODESET
104: | StaticProperty.NON_CREATIVE;
105: if ((getNumberOfArguments() == 2)
106: || (argument[2].getSpecialProperties() & StaticProperty.CONTEXT_DOCUMENT_NODESET) != 0) {
107: prop |= StaticProperty.CONTEXT_DOCUMENT_NODESET;
108: }
109: return prop;
110: }
111:
112: /**
113: * preEvaluate: this method suppresses compile-time evaluation by doing nothing
114: */
115:
116: public Expression preEvaluate(StaticContext env) {
117: return this ;
118: }
119:
120: /**
121: * Enumerate the results of the expression
122: */
123:
124: public SequenceIterator iterate(XPathContext context)
125: throws XPathException {
126:
127: Controller controller = context.getController();
128:
129: Item arg2 = argument[2].evaluateItem(context);
130: if (!(arg2 instanceof NodeInfo)) {
131: dynamicError(
132: "When calling the key() function, the context item must be a node",
133: "XTDE1270", context);
134: return null;
135: }
136: NodeInfo origin = (NodeInfo) arg2;
137: NodeInfo root = origin.getRoot();
138: if (root.getNodeKind() != Type.DOCUMENT) {
139: dynamicError(
140: "In the key() function,"
141: + " the node supplied in the third argument (or the context node if absent)"
142: + " must be in a tree whose root is a document node",
143: "XTDE1270", context);
144: return null;
145: }
146: DocumentInfo doc = (DocumentInfo) root;
147:
148: int fprint = keyFingerprint;
149: if (fprint == -1) {
150: String givenkeyname = argument[0].evaluateItem(context)
151: .getStringValue();
152: try {
153: fprint = controller.getNamePool().allocateLexicalQName(
154: givenkeyname, false, nsContext,
155: controller.getConfiguration().getNameChecker())
156: & NamePool.FP_MASK;
157: } catch (XPathException err) {
158: dynamicError("Invalid key name: " + err.getMessage(),
159: "XTDE1260", context);
160: }
161: if (fprint == -1) {
162: dynamicError("Key '" + givenkeyname
163: + "' has not been defined", "XTDE1260", context);
164: return null;
165: }
166: }
167:
168: // if (internal) {
169: // System.err.println("Using key " + fprint + " on doc " + doc);
170: // }
171:
172: // If the second argument is a singleton, we evaluate the function
173: // directly; otherwise we recurse to evaluate it once for each Item
174: // in the sequence.
175:
176: Expression expression = argument[1];
177: SequenceIterator allResults;
178: if (Cardinality.allowsMany(expression.getCardinality())) {
179: KeyMappingFunction map = new KeyMappingFunction();
180: map.document = doc;
181: map.keyContext = context;
182: map.keyFingerprint = fprint;
183:
184: SequenceIterator keys = argument[1].iterate(context);
185: SequenceIterator allValues = new MappingIterator(keys, map,
186: null);
187: allResults = new DocumentOrderIterator(allValues,
188: LocalOrderComparer.getInstance());
189: } else {
190: AtomicValue keyValue = (AtomicValue) argument[1]
191: .evaluateItem(context);
192: if (keyValue == null) {
193: return EmptyIterator.getInstance();
194: }
195: KeyManager keyManager = controller.getKeyManager();
196: allResults = keyManager.selectByKey(fprint, doc, keyValue,
197: context);
198: }
199: if (origin == doc) {
200: return allResults;
201: }
202: SubtreeFilter filter = new SubtreeFilter();
203: filter.origin = origin;
204: return new MappingIterator(allResults, filter, null);
205: }
206:
207: /**
208: * Implement the MappingFunction interface
209: */
210:
211: private static class KeyMappingFunction implements MappingFunction {
212:
213: public XPathContext keyContext;
214: public int keyFingerprint;
215: public DocumentInfo document;
216:
217: public Object map(Item item, XPathContext context)
218: throws XPathException {
219: KeyManager keyManager = keyContext.getController()
220: .getKeyManager();
221: return keyManager.selectByKey(keyFingerprint, document,
222: (AtomicValue) item, keyContext);
223: }
224: }
225:
226: /**
227: * Mapping class to filter nodes that have the origin node as an ancestor-or-self
228: */
229:
230: private static class SubtreeFilter implements MappingFunction {
231:
232: public NodeInfo origin;
233:
234: // TODO: much more efficient implementations are possible, especially with the TinyTree
235:
236: public Object map(Item item, XPathContext context)
237: throws XPathException {
238: if (isAncestorOrSelf(origin, (NodeInfo) item)) {
239: return item;
240: } else {
241: return null;
242: }
243: }
244:
245: /**
246: * Test if one node is an ancestor-or-self of another
247: * @param a the putative ancestor-or-self node
248: * @param d the putative descendant node
249: * @return true if a is an ancestor-or-self of d
250: */
251:
252: private static boolean isAncestorOrSelf(NodeInfo a, NodeInfo d) {
253: NodeInfo p = d;
254: while (p != null) {
255: if (a.isSameNodeInfo(p)) {
256: return true;
257: }
258: p = p.getParent();
259: }
260: return false;
261: }
262: }
263:
264: }
265:
266: //
267: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
268: // you may not use this file except in compliance with the License. You may obtain a copy of the
269: // License at http://www.mozilla.org/MPL/
270: //
271: // Software distributed under the License is distributed on an "AS IS" basis,
272: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
273: // See the License for the specific language governing rights and limitations under the License.
274: //
275: // The Original Code is: all this file.
276: //
277: // The Initial Developer of the Original Code is Michael H. Kay.
278: //
279: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
280: //
281: // Contributor(s): none.
282: //
|