001: package net.sf.saxon.instruct;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.Err;
005: import net.sf.saxon.event.ReceiverOptions;
006: import net.sf.saxon.event.SequenceReceiver;
007: import net.sf.saxon.expr.*;
008: import net.sf.saxon.om.*;
009: import net.sf.saxon.pattern.NodeKindTest;
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: import net.sf.saxon.value.QNameValue;
016: import net.sf.saxon.value.SequenceType;
017: import net.sf.saxon.value.StringValue;
018:
019: import java.io.PrintStream;
020: import java.util.ArrayList;
021: import java.util.Iterator;
022:
023: /**
024: * An instruction derived from an xsl:attribute element in stylesheet, or from
025: * an attribute constructor in XQuery
026: */
027:
028: public final class Attribute extends SimpleNodeConstructor {
029:
030: private Expression attributeName;
031: private Expression namespace = null;
032: private NamespaceResolver nsContext;
033: private SimpleType schemaType;
034: private int annotation;
035: private int validationAction;
036: private boolean allowNameAsQName;
037: private int options;
038:
039: /**
040: * Construct an Attribute instruction
041: * @param attributeName An expression to calculate the attribute name
042: * @param namespace An expression to calculate the attribute namespace
043: * @param nsContext a NamespaceContext object containing the static namespace context of the
044: * stylesheet instruction
045: * @param validationAction e.g. validation=strict, lax, strip, preserve
046: * @param schemaType Type against which the attribute must be validated. This must not be a namespace-sensitive
047: * type; it is the caller's responsibility to check this.
048: * @param annotation Integer code identifying the type named in the <code>type</code> attribute
049: * @param allowNameAsQName
050: */
051:
052: public Attribute(Expression attributeName, Expression namespace,
053: NamespaceResolver nsContext, int validationAction,
054: SimpleType schemaType, int annotation,
055: boolean allowNameAsQName) {
056: this .attributeName = attributeName;
057: this .namespace = namespace;
058: this .nsContext = nsContext;
059: this .schemaType = schemaType;
060: if (annotation == -1) {
061: this .annotation = StandardNames.XDT_UNTYPED_ATOMIC;
062: } else {
063: this .annotation = annotation;
064: }
065: this .validationAction = validationAction;
066: this .options = 0;
067: this .allowNameAsQName = allowNameAsQName;
068: adoptChildExpression(attributeName);
069: adoptChildExpression(namespace);
070: }
071:
072: /**
073: * Indicate that two attributes with the same name are not acceptable.
074: * (This option is set in XQuery, but not in XSLT)
075: */
076:
077: public void setRejectDuplicates() {
078: this .options |= ReceiverOptions.REJECT_DUPLICATES;
079: }
080:
081: /**
082: * Get the name of this instruction
083: */
084:
085: public int getInstructionNameCode() {
086: return StandardNames.XSL_ATTRIBUTE;
087: }
088:
089: public ItemType getItemType(TypeHierarchy th) {
090: return NodeKindTest.ATTRIBUTE;
091: }
092:
093: public int getCardinality() {
094: return StaticProperty.EXACTLY_ONE;
095: }
096:
097: /**
098: * Get the static properties of this expression (other than its type). The result is
099: * bit-signficant. These properties are used for optimizations. In general, if
100: * property bit is set, it is true, but if it is unset, the value is unknown.
101: *
102: * @return a set of flags indicating static properties of this expression
103: */
104:
105: public int computeSpecialProperties() {
106: return super .computeSpecialProperties()
107: | StaticProperty.SINGLE_DOCUMENT_NODESET;
108: }
109:
110: public Expression simplify(StaticContext env) throws XPathException {
111: attributeName = attributeName.simplify(env);
112: if (namespace != null) {
113: namespace = namespace.simplify(env);
114: }
115: return super .simplify(env);
116: }
117:
118: public void localTypeCheck(StaticContext env,
119: ItemType contextItemType) throws XPathException {
120: attributeName = attributeName.typeCheck(env, contextItemType);
121: adoptChildExpression(attributeName);
122:
123: RoleLocator role = new RoleLocator(RoleLocator.INSTRUCTION,
124: "attribute/name", 0, null);
125: role.setSourceLocator(this );
126:
127: if (allowNameAsQName) {
128: // Can only happen in XQuery
129: attributeName = TypeChecker.staticTypeCheck(attributeName,
130: SequenceType.SINGLE_ATOMIC, false, role, env);
131: } else {
132: attributeName = TypeChecker.staticTypeCheck(attributeName,
133: SequenceType.SINGLE_STRING, false, role, env);
134: }
135:
136: if (namespace != null) {
137: namespace.typeCheck(env, contextItemType);
138: adoptChildExpression(namespace);
139:
140: role = new RoleLocator(RoleLocator.INSTRUCTION,
141: "attribute/namespace", 0, null);
142: role.setSourceLocator(this );
143: namespace = TypeChecker.staticTypeCheck(namespace,
144: SequenceType.SINGLE_STRING, false, role, env);
145: }
146: }
147:
148: /**
149: * Get the subexpressions of this expression
150: * @return an iterator over the subexpressions
151: */
152:
153: public Iterator iterateSubExpressions() {
154: ArrayList list = new ArrayList(10);
155: if (select != null) {
156: list.add(select);
157: }
158: list.add(attributeName);
159: if (namespace != null) {
160: list.add(namespace);
161: }
162: return list.iterator();
163: }
164:
165: /**
166: * Offer promotion for subexpressions. The offer will be accepted if the subexpression
167: * is not dependent on the factors (e.g. the context item) identified in the PromotionOffer.
168: * By default the offer is not accepted - this is appropriate in the case of simple expressions
169: * such as constant values and variable references where promotion would give no performance
170: * advantage. This method is always called at compile time.
171: *
172: * @param offer details of the offer, for example the offer to move
173: * expressions that don't depend on the context to an outer level in
174: * the containing expression
175: * @exception XPathException if any error is detected
176: */
177:
178: protected void promoteInst(PromotionOffer offer)
179: throws XPathException {
180: attributeName = doPromotion(attributeName, offer);
181: if (namespace != null) {
182: namespace = doPromotion(namespace, offer);
183: }
184: super .promoteInst(offer);
185: }
186:
187: /**
188: * Check that any elements and attributes constructed or returned by this expression are acceptable
189: * in the content model of a given complex type. It's always OK to say yes, since the check will be
190: * repeated at run-time. The process of checking element and attribute constructors against the content
191: * model of a complex type also registers the type of content expected of those constructors, so the
192: * static validation can continue recursively.
193: */
194:
195: public void checkPermittedContents(SchemaType parentType,
196: StaticContext env, boolean whole) throws XPathException {
197: if (parentType instanceof SimpleType) {
198: StaticError err = new StaticError(
199: "Attributes are not permitted here: the containing element is of simple type "
200: + parentType.getDescription());
201: err.setIsTypeError(true);
202: err.setLocator(this );
203: throw err;
204: }
205: }
206:
207: /**
208: * Process this instruction
209: * @param context the dynamic context of the transformation
210: * @return a TailCall to be executed by the caller, always null for this instruction
211: */
212:
213: public TailCall processLeavingTail(XPathContext context)
214: throws XPathException {
215: int nameCode = evaluateNameCode(context);
216: if (nameCode == -1) {
217: return null;
218: }
219: SequenceReceiver out = context.getReceiver();
220: int opt = options;
221: int ann = annotation;
222:
223: // we may need to change the namespace prefix if the one we chose is
224: // already in use with a different namespace URI: this is done behind the scenes
225: // by the Outputter
226:
227: String value = expandChildren(context).toString();
228: if (schemaType != null) {
229: // test whether the value actually conforms to the given type
230: try {
231: XPathException err = schemaType.validateContent(value,
232: DummyNamespaceResolver.getInstance(), context
233: .getConfiguration().getNameChecker());
234: if (err != null) {
235: throw new ValidationException("Attribute value "
236: + Err.wrap(value, Err.VALUE)
237: + " does not match the required type "
238: + schemaType.getDescription() + ". "
239: + err.getMessage());
240: }
241: } catch (UnresolvedReferenceException ure) {
242: throw new ValidationException(ure);
243: }
244: } else if (validationAction == Validation.STRICT
245: || validationAction == Validation.LAX) {
246: try {
247: ann = context.getConfiguration().validateAttribute(
248: nameCode, value, validationAction);
249: } catch (ValidationException e) {
250: DynamicError err = DynamicError.makeDynamicError(e);
251: err.setErrorCode(e.getErrorCodeLocalPart());
252: err.setXPathContext(context);
253: err.setLocator(this );
254: err.setIsTypeError(true);
255: throw err;
256: }
257: }
258: try {
259: out.attribute(nameCode, ann, value, locationId, opt);
260: } catch (XPathException err) {
261: throw dynamicError(this , err, context);
262: }
263:
264: return null;
265: }
266:
267: protected int evaluateNameCode(XPathContext context)
268: throws XPathException, XPathException {
269: NamePool pool = context.getNamePool();
270:
271: Item nameValue = attributeName.evaluateItem(context);
272:
273: String prefix = null;
274: String localName = null;
275:
276: if (nameValue instanceof StringValue) {
277: // this will always be the case in XSLT
278: CharSequence rawName = nameValue.getStringValueCS();
279: try {
280: String[] parts = context.getConfiguration()
281: .getNameChecker().getQNameParts(rawName);
282: prefix = parts[0];
283: localName = parts[1];
284: } catch (QNameException err) {
285: DynamicError err1 = new DynamicError(
286: "Invalid attribute name: " + rawName, this );
287: err1.setErrorCode("XTDE0850");
288: err1.setXPathContext(context);
289: throw dynamicError(this , err1, context);
290: }
291: if (rawName.equals("xmlns")) {
292: if (namespace == null) {
293: DynamicError err = new DynamicError(
294: "Invalid attribute name: " + rawName, this );
295: if (context.getController().getExecutable()
296: .getHostLanguage() == Configuration.XQUERY) {
297: err.setErrorCode("XQDY0044");
298: } else {
299: err.setErrorCode("XTDE0855");
300: }
301: err.setXPathContext(context);
302: throw dynamicError(this , err, context);
303: }
304: }
305: if (prefix.equals("xmlns")) {
306: if (namespace == null) {
307: DynamicError err = new DynamicError(
308: "Invalid attribute name: " + rawName, this );
309: if (context.getController().getExecutable()
310: .getHostLanguage() == Configuration.XQUERY) {
311: err.setErrorCode("XQDY0044");
312: } else {
313: err.setErrorCode("XTDE0860");
314: }
315: err.setXPathContext(context);
316: throw dynamicError(this , err, context);
317: } else {
318: // ignore the prefix "xmlns"
319: prefix = "";
320: }
321: }
322:
323: } else if (nameValue instanceof QNameValue) {
324: // this is allowed in XQuery
325: localName = ((QNameValue) nameValue).getLocalName();
326: String namespaceURI = ((QNameValue) nameValue)
327: .getNamespaceURI();
328: if (namespaceURI == null) {
329: namespaceURI = "";
330: }
331: namespace = new StringValue(namespaceURI);
332: if (namespaceURI.equals("")) {
333: prefix = "";
334: } else {
335: prefix = ((QNameValue) nameValue).getPrefix();
336: // If the prefix is a duplicate, a different one will be substituted
337: }
338:
339: } else {
340: DynamicError err = new DynamicError(
341: "Attribute name must be either a string or a QName",
342: this );
343: err.setErrorCode("XPTY0004");
344: err.setIsTypeError(true);
345: err.setXPathContext(context);
346: throw dynamicError(this , err, context);
347: }
348:
349: String uri;
350:
351: if (namespace == null) {
352: if ("".equals(prefix)) {
353: uri = "";
354: } else {
355: uri = nsContext.getURIForPrefix(prefix, false);
356: if (uri == null) {
357: DynamicError err = new DynamicError(
358: "Undeclared prefix in attribute name: "
359: + prefix, this );
360: err.setErrorCode((isXSLT(context) ? "XTDE0860"
361: : "XQDY0074"));
362: err.setXPathContext(context);
363: throw dynamicError(this , err, context);
364: }
365: }
366:
367: } else {
368:
369: // generate a name using the supplied namespace URI
370:
371: uri = namespace.evaluateAsString(context);
372: if ("".equals(uri)) {
373: // there is a special rule for this case in the specification;
374: // we force the attribute to go in the null namespace
375: prefix = "";
376:
377: } else {
378: // if a suggested prefix is given, use it; otherwise try to find a prefix
379: // associated with this URI; if all else fails, invent one.
380: if ("".equals(prefix)) {
381: prefix = pool.suggestPrefixForURI(uri);
382: if (prefix == null) {
383: prefix = "ns0";
384: // this will be replaced later if it is already in use
385: }
386: }
387: }
388: }
389:
390: return pool.allocate(prefix, uri, localName);
391: }
392:
393: /**
394: * Display this instruction as an expression, for diagnostics
395: */
396:
397: public void display(int level, NamePool pool, PrintStream out) {
398: out.println(ExpressionTool.indent(level) + "attribute ");
399: out.println(ExpressionTool.indent(level + 1) + "name");
400: attributeName.display(level + 2, pool, out);
401: super .display(level + 1, pool, out);
402: }
403: }
404:
405: //
406: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
407: // you may not use this file except in compliance with the License. You may obtain a copy of the
408: // License at http://www.mozilla.org/MPL/
409: //
410: // Software distributed under the License is distributed on an "AS IS" basis,
411: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
412: // See the License for the specific language governing rights and limitations under the License.
413: //
414: // The Original Code is: all this file.
415: //
416: // The Initial Developer of the Original Code is Michael H. Kay.
417: //
418: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
419: //
420: // Contributor(s): none.
421: //
|