001: // Copyright (c) 2005, 2006 Per M.A. Bothner.
002: // This is free software; for terms and warranty disclaimer see ./COPYING.
003:
004: package gnu.xquery.util;
005:
006: import gnu.mapping.*;
007: import gnu.xml.*;
008: import gnu.kawa.xml.*;
009: import gnu.lists.*;
010: import java.util.Stack;
011: import java.net.*;
012: import gnu.expr.PrimProcedure;
013: import gnu.bytecode.ClassType;
014: import gnu.xquery.lang.XQuery;
015: import gnu.text.Path;
016:
017: public class NodeUtils {
018: public static Object nodeName(Object node) {
019: if (node == Values.empty || node == null)
020: return node;
021: if (!(node instanceof KNode))
022: throw new WrongType("node-name", 1, node, "node()?");
023: Object sym = ((KNode) node).getNodeSymbol();
024: if (sym == null)
025: return Values.empty;
026: else
027: return sym;
028: }
029:
030: public static String name(Object node) {
031: if (node == Values.empty || node == null)
032: return "";
033: Object name = ((KNode) node).getNodeNameObject();
034: if (name == null || name == Values.empty)
035: return "";
036: return name.toString();
037: }
038:
039: public static String localName(Object node) {
040: if (node == Values.empty || node == null)
041: return "";
042: if (!(node instanceof KNode))
043: throw new WrongType("local-name", 1, node, "node()?");
044: Object name = ((KNode) node).getNodeNameObject();
045: if (name == null || name == Values.empty)
046: return "";
047: if (name instanceof Symbol)
048: return ((Symbol) name).getName();
049: return name.toString();
050: }
051:
052: public static Object namespaceURI(Object node) {
053: if (node != Values.empty && node != null) {
054: if (!(node instanceof KNode))
055: throw new WrongType("namespace-uri", 1, node, "node()?");
056: Object name = ((KNode) node).getNodeNameObject();
057: if (name instanceof Symbol)
058: return QNameUtils.namespaceURIFromQName(name);
059: }
060: return "";
061: }
062:
063: public static void prefixesFromNodetype(XName name, Consumer out) {
064: NamespaceBinding bindings = ((XName) name).getNamespaceNodes();
065: for (NamespaceBinding ns = bindings; ns != null; ns = ns
066: .getNext()) {
067: String prefix = ns.getPrefix();
068: // Check for duplicates. This is an O(n^2) algorthm, but these
069: // lists are usually quite short ...
070: for (NamespaceBinding ns2 = bindings;; ns2 = ns2.getNext()) {
071: if (ns2 == ns) {
072: out.writeObject(prefix == null ? "" : prefix);
073: break;
074: }
075: if (ns2.getPrefix() == prefix) {
076: // Previously written.
077: break;
078: }
079: }
080: }
081: }
082:
083: public static void inScopePrefixes$X(Object node, CallContext ctx) {
084: //if (node instanceof KElement)
085: {
086: KElement element = (KElement) node;
087: Object type = element.getNodeNameObject();
088: if (type instanceof XName)
089: prefixesFromNodetype((XName) type, ctx.consumer);
090: else
091: ctx.consumer.writeObject("xml");
092: }
093: }
094:
095: public static void data$X(Object arg, CallContext ctx) {
096: Consumer out = ctx.consumer;
097: if (arg instanceof Values) {
098: Values vals = (Values) arg;
099: int ipos = vals.startPos();
100: while ((ipos = vals.nextPos(ipos)) != 0)
101: out.writeObject(KNode.atomicValue(vals
102: .getPosPrevious(ipos)));
103: } else
104: out.writeObject(KNode.atomicValue(arg));
105: }
106:
107: /** Return the root node of the argument. */
108: public static Object root(Object arg) {
109: if (arg == null || arg == Values.empty)
110: return arg;
111: if (!(arg instanceof KNode))
112: throw new WrongType("root", 1, arg, "node()?");
113: KNode node = (KNode) arg;
114: return Nodes.root((NodeTree) node.sequence, node.getPos());
115: }
116:
117: /** Return root node, coerced to a document node.
118: * Used to implement '/'-rooted path expressions.
119: */
120: public static KDocument rootDocument(Object arg) {
121: if (!(arg instanceof KNode))
122: throw new WrongType("root-document", 1, arg, "node()?");
123: KNode node = (KNode) arg;
124: node = Nodes.root((NodeTree) node.sequence, node.getPos());
125: if (!(node instanceof KDocument))
126: throw new WrongType("root-document", 1, arg, "document()");
127: return (KDocument) node;
128: }
129:
130: public static String getLang(KNode node) {
131: NodeTree seq = (NodeTree) node.sequence;
132: int attr = seq.ancestorAttribute(node.ipos,
133: gnu.xml.NamespaceBinding.XML_NAMESPACE, "lang");
134: if (attr == 0)
135: return null;
136: else
137: return KNode.getNodeValue(seq, attr);
138: }
139:
140: public static boolean lang(Object testlang, Object node) {
141: String teststr;
142: if (testlang == null || testlang == Values.empty)
143: teststr = "";
144: else
145: teststr = TextUtils.stringValue(testlang);
146: String lang = getLang((KNode) node);
147: if (lang == null)
148: return false;
149: int langlen = lang.length();
150: int testlen = teststr.length();
151: if (langlen > testlen && lang.charAt(testlen) == '-')
152: lang = lang.substring(0, testlen);
153: return lang.equalsIgnoreCase(teststr);
154: }
155:
156: public static Object documentUri(Object arg) {
157: if (arg == null || arg == Values.empty)
158: return arg;
159: if (!(arg instanceof KNode))
160: throw new WrongType("xs:document-uri", 1, arg, "node()?");
161: KNode node = (KNode) arg;
162: Object uri = ((NodeTree) node.sequence)
163: .documentUriOfPos(node.ipos);
164: return uri == null ? Values.empty : uri;
165: }
166:
167: public static Object nilled(Object arg) {
168: if (arg == null || arg == Values.empty)
169: return arg;
170: if (!(arg instanceof KNode))
171: throw new WrongType("nilled", 1, arg, "node()?");
172: if (!(arg instanceof KElement))
173: return Values.empty;
174: return Boolean.FALSE;
175: }
176:
177: public static Object baseUri(Object arg) {
178: if (arg == null || arg == Values.empty)
179: return arg;
180: if (!(arg instanceof KNode))
181: throw new WrongType("base-uri", 1, arg, "node()?");
182: Path uri = ((KNode) arg).baseURI();
183: if (uri == null)
184: return Values.empty;
185: else
186: return uri;
187: }
188:
189: /* #ifdef JAVA5 */
190: // @SuppressWarnings("unchecked")
191: /* #endif */
192: /** Extract canditate IDREFs from arg.
193: * @return {@code null} (if no {@code IDREF}s);
194: * a {@code String} (if a single {@code IDREF});
195: * or a {@code Stack<String>} (if more than one {@code IDREF}s).
196: */
197: static Object getIDs(Object arg, Object collector) {
198: if (arg instanceof KNode)
199: arg = KNode.atomicValue(arg);
200: if (arg instanceof Values) {
201: Object[] ar = ((Values) arg).getValues();
202: for (int i = ar.length; --i >= 0;)
203: collector = getIDs(ar[i], collector);
204: } else {
205: String str = StringUtils
206: .coerceToString(arg, "fn:id", 1, "");
207: int len = str.length();
208: int i = 0;
209: while (i < len) {
210: char ch = str.charAt(i++);
211: if (Character.isWhitespace(ch))
212: continue;
213: int start = XName.isNameStart(ch) ? i - 1 : len;
214: while (i < len) {
215: ch = str.charAt(i);
216: if (Character.isWhitespace(ch))
217: break;
218: i++;
219: if (start < len && !XName.isNamePart(ch))
220: start = len;
221: }
222: if (start < len) {
223: String ref = str.substring(start, i);
224: if (collector == null)
225: collector = ref;
226: else {
227: Stack st;
228: if (collector instanceof Stack)
229: st = (Stack) collector;
230: else {
231: st = new Stack();
232: st.push(collector);
233: collector = st;
234: }
235: st.push(ref);
236: }
237: }
238: i++;
239: }
240: }
241: return collector;
242: }
243:
244: public static void id$X(Object arg1, Object arg2, CallContext ctx) {
245: KNode node = (KNode) arg2;
246: NodeTree ntree = (NodeTree) node.sequence;
247: KDocument root = (KDocument) Nodes.root(ntree, node.ipos);
248: Consumer out = ctx.consumer;
249: Object idrefs = getIDs(arg1, null);
250: if (idrefs == null)
251: return;
252: ntree.makeIDtableIfNeeded();
253: if (out instanceof PositionConsumer
254: && (idrefs instanceof String || out instanceof SortedNodes))
255: idScan(idrefs, ntree, (PositionConsumer) out);
256: else if (idrefs instanceof String) {
257: int pos = ntree.lookupID((String) idrefs);
258: if (pos != -1)
259: out.writeObject(KNode.make(ntree, pos));
260: } else {
261: SortedNodes nodes = new SortedNodes();
262: idScan(idrefs, ntree, nodes);
263: Values.writeValues(nodes, out);
264: }
265: }
266:
267: private static void idScan(Object ids, NodeTree seq,
268: PositionConsumer out) {
269: if (ids instanceof String) {
270: int pos = seq.lookupID((String) ids);
271: if (pos != -1)
272: out.writePosition(seq, pos);
273: } else if (ids instanceof Stack) {
274: Stack st = (Stack) ids;
275: int n = st.size();
276: for (int i = 0; i < n; i++)
277: idScan(st.elementAt(i), seq, out);
278: }
279: }
280:
281: public static Object idref(Object arg1, Object arg2) {
282: KNode node = (KNode) arg2;
283: KDocument root = (KDocument) Nodes.root(
284: (NodeTree) node.sequence, node.getPos());
285: return Values.empty;
286: }
287:
288: /** Internal namespace used to manage cached collections. */
289: static String collectionNamespace = "http://gnu.org/kawa/cached-collections";
290:
291: /** Add a uri-to-value binding that setSavedCollection can later return. */
292: public static void setSavedCollection(Object uri, Object value,
293: Environment env) {
294: if (uri == null)
295: uri = "#default";
296: Symbol sym = Symbol.make(collectionNamespace, uri.toString());
297: env.put(sym, null, value);
298: }
299:
300: /** Add a uri-to-value binding that setSavedCollection can later return. */
301: public static void setSavedCollection(Object uri, Object value) {
302: setSavedCollection(uri, value, Environment.getCurrent());
303: }
304:
305: /** Default resolver for fn:collection.
306: * Return nodes previously bound using setSavedCollection.
307: */
308: public static Object getSavedCollection(Object uri, Environment env) {
309: if (uri == null)
310: uri = "#default";
311: Symbol sym = Symbol.make(collectionNamespace, uri.toString());
312: Object coll = env.get(sym, null, null);
313: if (coll == null)
314: throw new RuntimeException("collection '" + uri
315: + "' not found");
316: return coll;
317: }
318:
319: /** Default resolver for fn:collection.
320: * Return nodes previously bound using setSavedCollection.
321: */
322: public static Object getSavedCollection(Object uri) {
323: return getSavedCollection(uri, Environment.getCurrent());
324: }
325:
326: /** Symbol used to bind a collection resolver. */
327: public static final Symbol collectionResolverSymbol = Symbol.make(
328: XQuery.LOCAL_NAMESPACE, "collection-resolver", "qexo");
329:
330: public static Object collection(Object uri, Object base)
331: throws Throwable {
332: uri = resolve(uri, base, "collection");
333: Environment env = Environment.getCurrent();
334: Symbol rsym = NodeUtils.collectionResolverSymbol;
335: Object rvalue = env.get(rsym, null, null);
336: if (rvalue == null) {
337: rvalue = env.get(Symbol.makeWithUnknownNamespace(rsym
338: .getLocalName(), rsym.getPrefix()), null, null);
339: }
340: String str;
341: int colon;
342: if (rvalue == null) {
343: return getSavedCollection(uri);
344: } else if ((rvalue instanceof String || rvalue instanceof UntypedAtomic)
345: && (colon = (str = rvalue.toString()).indexOf(':')) > 0) {
346: String cname = str.substring(0, colon);
347: String mname = str.substring(colon + 1);
348: Class rclass;
349: try {
350: rclass = Class.forName(cname);
351: } catch (ClassNotFoundException ex) {
352: throw new RuntimeException(
353: "invalid collection-resolver: class " + cname
354: + " not found");
355: } catch (Throwable ex) {
356: throw new RuntimeException(
357: "invalid collection-resolver: " + ex);
358: }
359: ClassType rclassType = ClassType.make(cname);
360: rvalue = gnu.kawa.reflect.ClassMethods.apply(rclassType,
361: mname, '\0', XQuery.instance);
362: if (rvalue == null)
363: throw new RuntimeException(
364: "invalid collection-resolver: no method "
365: + mname + " in " + cname);
366: }
367: if (!(rvalue instanceof Procedure))
368: throw new RuntimeException("invalid collection-resolver: "
369: + rvalue);
370: return ((Procedure) rvalue).apply1(uri);
371: }
372:
373: static Object resolve(Object uri, Object base, String fname)
374: throws Throwable {
375: if (!(uri instanceof java.io.File) && !(uri instanceof Path)
376: /* #ifdef use:java.net.URI */
377: && !(uri instanceof URI)
378: /* #endif */
379: && !(uri instanceof URL))
380: uri = StringUtils.coerceToString(uri, fname, 1, null);
381: if (uri == Values.empty || uri == null)
382: return null;
383: return Path.currentPath().resolve(Path.valueOf(uri));
384: }
385:
386: /** Parse an XML document, caching the result.
387: * Only positive results are cached; failures are not.)
388: * This implements the standard XQuery <code>fn:doc</code> function.
389: */
390: public static Object docCached(Object uri, Object base)
391: throws Throwable {
392: uri = resolve(uri, base, "doc");
393: if (uri == null)
394: return Values.empty;
395: return Document.parseCached(uri);
396: }
397:
398: /** Check if an XML document is available, caching the result.
399: * Only positive results are cached; failures are not. Thus it is possible
400: * for a false result to be followed by a true result, but not vice versa.
401: * This implements the standard XQuery <code>fn:doc-available</code> function.
402: */
403: public static boolean availableCached(Object uri, Object base)
404: throws Throwable {
405: uri = resolve(uri, base, "doc-available");
406: if (uri == null)
407: return false;
408: try {
409: Document.parseCached(uri);
410: return true;
411: } catch (Throwable ex) {
412: return false;
413: }
414: }
415: }
|