001: package net.sf.saxon.value;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.expr.StaticContext;
005: import net.sf.saxon.expr.XPathContext;
006: import net.sf.saxon.functions.Component;
007: import net.sf.saxon.om.NameChecker;
008: import net.sf.saxon.om.NamePool;
009: import net.sf.saxon.om.QNameException;
010: import net.sf.saxon.style.StandardNames;
011: import net.sf.saxon.trans.DynamicError;
012: import net.sf.saxon.trans.StaticError;
013: import net.sf.saxon.trans.XPathException;
014: import net.sf.saxon.type.*;
015:
016: import java.lang.reflect.Constructor;
017: import java.lang.reflect.InvocationTargetException;
018:
019: /**
020: * A QName value. This implements the so-called "triples proposal", in which the prefix is retained as
021: * part of the value. The prefix is not used in any operation on a QName other than conversion of the
022: * QName to a string.
023: */
024:
025: public class QNameValue extends AtomicValue {
026:
027: private String prefix; // "" for the default prefix
028: private String uri; // "" for the null namespace
029: private String localPart;
030:
031: // Note: an alternative design was considered in which the QName was represented by a NamePool and a
032: // nameCode. This caused difficulties because there is not always enough context information available
033: // when creating a QName to locate the NamePool.
034:
035: /**
036: * Constructor
037: * @param namePool The name pool containing the specified name code
038: * @param nameCode The name code identifying this name in the name pool
039: */
040:
041: public QNameValue(NamePool namePool, int nameCode) {
042: prefix = namePool.getPrefix(nameCode);
043: uri = namePool.getURI(nameCode);
044: localPart = namePool.getLocalName(nameCode);
045: }
046:
047: /**
048: * Constructor. This constructor validates that the local part is a valid NCName.
049: * @param prefix The prefix part of the QName (not used in comparisons). Use null or "" to represent the
050: * default prefix.
051: * @param uri The namespace part of the QName. Use null or "" to represent the null namespace.
052: * @param localName The local part of the QName
053: * @param checker NameChecker used to check the name against XML 1.0 or XML 1.1 rules. Supply null
054: * if the name does not need to be checked (the caller asserts that it is known to be valid)
055: */
056:
057: public QNameValue(String prefix, String uri, String localName,
058: NameChecker checker) throws XPathException {
059: if (checker != null && !checker.isValidNCName(localName)) {
060: DynamicError err = new DynamicError(
061: "Malformed local name in QName: '" + localName
062: + '\'');
063: err.setErrorCode("FORG0001");
064: throw err;
065: }
066: this .prefix = (prefix == null ? "" : prefix);
067: this .uri = (uri == null ? "" : uri);
068: if (checker != null && this .uri.equals("")
069: && !this .prefix.equals("")) {
070: DynamicError err = new DynamicError(
071: "QName has null namespace but non-empty prefix");
072: err.setErrorCode("FOCA0002");
073: throw err;
074: }
075: this .localPart = localName;
076: }
077:
078: /**
079: * Create a QName value (possibly a DerivedAtomicValue derived from QName) from
080: * a string literal, given a namespace context
081: * @param operand the input string
082: * @param targetType the type required: QName, or a type derived from QName or NOTATION
083: * @param env the static context, including the namespace context
084: * @return the value after conversion
085: * @throws XPathException if the name is lexically invalid or uses an undeclared prefix
086: */
087:
088: public static AtomicValue castToQName(StringValue operand,
089: AtomicType targetType, StaticContext env)
090: throws XPathException {
091: try {
092: CharSequence arg = operand.getStringValueCS();
093: String parts[] = env.getConfiguration().getNameChecker()
094: .getQNameParts(arg);
095: String uri;
096: if ("".equals(parts[0])) {
097: uri = "";
098: } else {
099: uri = env.getURIForPrefix(parts[0]);
100: if (uri == null) {
101: StaticError e = new StaticError("Prefix '"
102: + parts[0] + "' has not been declared");
103: throw e;
104: }
105: }
106: final TypeHierarchy th = env.getNamePool()
107: .getTypeHierarchy();
108: if (targetType.getFingerprint() == StandardNames.XS_QNAME) {
109: return new QNameValue(parts[0], uri, parts[1], null);
110: } else if (th.isSubType(targetType, Type.QNAME_TYPE)) {
111: QNameValue q = new QNameValue(parts[0], uri, parts[1],
112: null);
113: AtomicValue av = targetType.makeDerivedValue(q, arg,
114: true);
115: if (av instanceof ValidationErrorValue) {
116: throw ((ValidationErrorValue) av).getException();
117: }
118: return av;
119: } else {
120: NotationValue n = new NotationValue(parts[0], uri,
121: parts[1], null);
122: AtomicValue av = targetType.makeDerivedValue(n, arg,
123: true);
124: if (av instanceof ValidationErrorValue) {
125: throw ((ValidationErrorValue) av).getException();
126: }
127: return av;
128: }
129: } catch (QNameException err) {
130: StaticError e = new StaticError(err);
131: throw e;
132: }
133: }
134:
135: /**
136: * Get the string value as a String. Returns the QName as a lexical QName, retaining the original
137: * prefix if available.
138: */
139:
140: public String getStringValue() {
141: if ("".equals(prefix)) {
142: return localPart;
143: } else {
144: return prefix + ':' + localPart;
145: }
146: }
147:
148: /**
149: * Get the value as a JAXP QName
150: */
151:
152: // public QName getQName() {
153: // return new QName(uri, localPart, prefix);
154: // }
155: /**
156: * Get the name in Clark notation, that is {uri}local
157: */
158:
159: public String getClarkName() {
160: if ("".equals(uri)) {
161: return localPart;
162: } else {
163: return '{' + uri + '}' + localPart;
164: }
165: }
166:
167: /**
168: * Get the local part
169: */
170:
171: public String getLocalName() {
172: return localPart;
173: }
174:
175: /**
176: * Get the namespace part (null means no namespace)
177: */
178:
179: public String getNamespaceURI() {
180: return ("".equals(uri) ? null : uri);
181: }
182:
183: /**
184: * Get the prefix
185: */
186:
187: public String getPrefix() {
188: return prefix;
189: }
190:
191: /**
192: * Allocate a nameCode for this QName in the NamePool
193: * @param pool the NamePool to be used
194: * @return the allocated nameCode
195: */
196:
197: public int allocateNameCode(NamePool pool) {
198: return pool.allocate(prefix, uri, localPart);
199: }
200:
201: /**
202: * Get a component. Returns a zero-length string if the namespace-uri component is
203: * requested and is not present.
204: * @param part either Component.LOCALNAME or Component.NAMESPACE indicating which
205: * component of the value is required
206: * @return either the local name or the namespace URI, in each case as a StringValue
207: */
208:
209: public AtomicValue getComponent(int part) {
210: if (part == Component.LOCALNAME) {
211: return RestrictedStringValue.makeRestrictedString(
212: localPart, StandardNames.XS_NCNAME, null);
213: } else if (part == Component.NAMESPACE) {
214: return new AnyURIValue(uri);
215: } else if (part == Component.PREFIX) {
216: if ("".equals(prefix)) {
217: return null;
218: } else {
219: return RestrictedStringValue.makeRestrictedString(
220: prefix, StandardNames.XS_NCNAME, null);
221: }
222: } else {
223: throw new UnsupportedOperationException(
224: "Component of QName must be URI, Local Name, or Prefix");
225: }
226: }
227:
228: /**
229: * Convert to target data type
230: * @param requiredType an integer identifying the required atomic type
231: * @param context
232: * @return an AtomicValue, a value of the required type; or an ErrorValue
233: */
234:
235: public AtomicValue convertPrimitive(BuiltInAtomicType requiredType,
236: boolean validate, XPathContext context) {
237: switch (requiredType.getPrimitiveType()) {
238: case Type.ANY_ATOMIC:
239: case Type.ITEM:
240: case Type.QNAME:
241: return this ;
242: case Type.STRING:
243: return new StringValue(getStringValue());
244: case Type.UNTYPED_ATOMIC:
245: return new UntypedAtomicValue(getStringValue());
246: default:
247: ValidationException err = new ValidationException(
248: "Cannot convert QName to "
249: + requiredType.getDisplayName());
250: err.setErrorCode("XPTY0004");
251: err.setIsTypeError(true);
252: return new ValidationErrorValue(err);
253: }
254: }
255:
256: /**
257: * Return the type of the expression
258: * @return Type.QNAME (always)
259: * @param th
260: */
261:
262: public ItemType getItemType(TypeHierarchy th) {
263: return Type.QNAME_TYPE;
264: }
265:
266: /**
267: * Determine if two QName values are equal. This comparison ignores the prefix part
268: * of the value.
269: * @throws ClassCastException if they are not comparable
270: * @throws IllegalStateException if the two QNames are in different name pools
271: */
272:
273: public boolean equals(Object other) {
274: QNameValue val = (QNameValue) other;
275: return localPart.equals(val.localPart) && uri.equals(val.uri);
276: }
277:
278: public int hashCode() {
279: return localPart.hashCode() ^ uri.hashCode();
280: }
281:
282: /**
283: * Convert to Java object (for passing to external functions)
284: */
285:
286: public Object convertToJava(Class target, XPathContext context)
287: throws XPathException {
288: if (target.isAssignableFrom(QNameValue.class)) {
289: return this ;
290: } else if (target.getClass().getName().equals(
291: "javax.xml.namespace.QName")) {
292: // TODO: rewrite this under JDK 1.5
293: return makeQName(context.getConfiguration());
294: } else {
295: Object o = super .convertToJava(target, context);
296: if (o == null) {
297: throw new DynamicError("Conversion of QName to "
298: + target.getName() + " is not supported");
299: }
300: return o;
301: }
302: }
303:
304: /**
305: * The toString() method returns the name in the form QName("uri", "local")
306: * @return the name in in the form QName("uri", "local")
307: */
308:
309: public String toString() {
310: return "QName(\"" + uri + "\", \"" + localPart + "\")";
311: }
312:
313: /**
314: * Temporary method to construct a javax.xml.namespace.QName without actually mentioning it
315: * by name
316: */
317:
318: public Object makeQName(Configuration config) {
319: try {
320: Class qnameClass = config.getClass(
321: "javax.xml.namespace.QName", false, null);
322: Class[] argTypes = { String.class, String.class,
323: String.class };
324: Constructor constructor = qnameClass
325: .getConstructor(argTypes);
326: String[] argValues = { uri, localPart, prefix };
327: Object result = constructor.newInstance(argValues);
328: return result;
329: } catch (XPathException e) {
330: return null;
331: } catch (NoSuchMethodException e) {
332: return null;
333: } catch (InstantiationException e) {
334: return null;
335: } catch (IllegalAccessException e) {
336: return null;
337: } catch (InvocationTargetException e) {
338: return null;
339: }
340:
341: }
342:
343: // public static void main(String[] args) throws Exception {
344: // QName q = (QName)new QNameValue("a", "b", "c").makeQName();
345: // QNameValue v = Value.makeQNameValue(q);
346: // System.err.println(q);
347: // System.err.println(v);
348: // }
349:
350: }
351:
352: //
353: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
354: // you may not use this file except in compliance with the License. You may obtain a copy of the
355: // License at http://www.mozilla.org/MPL/
356: //
357: // Software distributed under the License is distributed on an "AS IS" basis,
358: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
359: // See the License for the specific language governing rights and limitations under the License.
360: //
361: // The Original Code is: all this file.
362: //
363: // The Initial Developer of the Original Code is Michael H. Kay
364: //
365: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
366: //
367: // Contributor(s): none.
368: //
|