001: package net.sf.saxon.instruct;
002:
003: import net.sf.saxon.Controller;
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.Item;
009: import net.sf.saxon.om.NamePool;
010: import net.sf.saxon.om.Orphan;
011: import net.sf.saxon.om.Validation;
012: import net.sf.saxon.pattern.NodeKindTest;
013: import net.sf.saxon.style.StandardNames;
014: import net.sf.saxon.trace.InstructionInfo;
015: import net.sf.saxon.trace.Location;
016: import net.sf.saxon.trans.DynamicError;
017: import net.sf.saxon.trans.StaticError;
018: import net.sf.saxon.trans.XPathException;
019: import net.sf.saxon.type.*;
020: import net.sf.saxon.value.AtomicValue;
021: import net.sf.saxon.value.StringValue;
022:
023: import java.io.PrintStream;
024:
025: /**
026: * An instruction derived from an xsl:attribute element in stylesheet, or from
027: * an attribute constructor in XQuery. This version deals only with attributes
028: * whose name is known at compile time. It is also used for attributes of
029: * literal result elements. The value of the attribute is in general computed
030: * at run-time.
031: */
032:
033: public final class FixedAttribute extends SimpleNodeConstructor {
034:
035: private int nameCode;
036: private SimpleType schemaType;
037: private int annotation;
038: private int options;
039: private int validationAction;
040:
041: /**
042: * Construct an Attribute instruction
043: * @param nameCode Represents the attribute name
044: * @param annotation Integer code identifying the type named in the <code>type</code> attribute
045: * of the instruction - zero if the attribute was not present
046: */
047:
048: public FixedAttribute(int nameCode, int validationAction,
049: SimpleType schemaType, int annotation) {
050: this .nameCode = nameCode;
051: this .schemaType = schemaType;
052: if (annotation == -1) {
053: this .annotation = StandardNames.XDT_UNTYPED_ATOMIC;
054: } else {
055: this .annotation = annotation;
056: }
057: this .validationAction = validationAction;
058: this .options = 0;
059: }
060:
061: /**
062: * Get the name of this instruction (return 'xsl:attribute')
063: */
064:
065: public int getInstructionNameCode() {
066: return StandardNames.XSL_ATTRIBUTE;
067: }
068:
069: /**
070: * Indicate that two attributes with the same name are not acceptable.
071: * (This option is set in XQuery, but not in XSLT)
072: */
073:
074: public void setRejectDuplicates() {
075: this .options |= ReceiverOptions.REJECT_DUPLICATES;
076: }
077:
078: /**
079: * Indicate that the attribute value contains no special characters that
080: * might need escaping
081: */
082:
083: public void setNoSpecialChars() {
084: this .options |= ReceiverOptions.NO_SPECIAL_CHARS;
085: }
086:
087: /**
088: * Set the expression defining the value of the attribute. If this is a constant, and if
089: * validation against a schema type was requested, the validation is done immediately.
090: * @param select The expression defining the content of the attribute
091: * @throws StaticError if the expression is a constant, and validation is requested, and
092: * the constant doesn't match the required type.
093: */
094: public void setSelect(Expression select) throws StaticError {
095: super .setSelect(select);
096:
097: // Attempt early validation if possible
098: if (select instanceof AtomicValue && schemaType != null
099: && !schemaType.isNamespaceSensitive()) {
100: CharSequence value = ((AtomicValue) select)
101: .getStringValueCS();
102: XPathException err = schemaType
103: .validateContent(value, DummyNamespaceResolver
104: .getInstance(), getExecutable()
105: .getConfiguration().getNameChecker());
106: if (err != null) {
107: throw new StaticError("Attribute value "
108: + Err.wrap(value, Err.VALUE)
109: + " does not the match the required type "
110: + schemaType.getDescription() + ". "
111: + err.getMessage());
112: }
113: }
114:
115: // If value is fixed, test whether there are any special characters that might need to be
116: // escaped when the time comes for serialization
117: if (select instanceof StringValue) {
118: boolean special = false;
119: CharSequence val = ((StringValue) select)
120: .getStringValueCS();
121: for (int k = 0; k < val.length(); k++) {
122: char c = val.charAt(k);
123: if ((int) c < 33 || (int) c > 126 || c == '<'
124: || c == '>' || c == '&' || c == '\"') {
125: special = true;
126: break;
127: }
128: }
129: if (!special) {
130: this .options |= ReceiverOptions.NO_SPECIAL_CHARS;
131: }
132: }
133: }
134:
135: public InstructionInfo getInstructionInfo() {
136: InstructionDetails details = (InstructionDetails) super
137: .getInstructionInfo();
138: details.setConstructType(Location.LITERAL_RESULT_ATTRIBUTE);
139: details.setObjectNameCode(nameCode);
140: return details;
141: }
142:
143: public ItemType getItemType(TypeHierarchy th) {
144: return NodeKindTest.ATTRIBUTE;
145: }
146:
147: public int getCardinality() {
148: return StaticProperty.EXACTLY_ONE;
149: }
150:
151: public void localTypeCheck(StaticContext env,
152: ItemType contextItemType) {
153: }
154:
155: protected int evaluateNameCode(XPathContext context) {
156: return nameCode;
157: }
158:
159: /**
160: * Check that any elements and attributes constructed or returned by this expression are acceptable
161: * in the content model of a given complex type. It's always OK to say yes, since the check will be
162: * repeated at run-time. The process of checking element and attribute constructors against the content
163: * model of a complex type also registers the type of content expected of those constructors, so the
164: * static validation can continue recursively.
165: */
166:
167: public void checkPermittedContents(SchemaType parentType,
168: StaticContext env, boolean whole) throws XPathException {
169: int fp = nameCode & NamePool.FP_MASK;
170: if (fp == StandardNames.XSI_TYPE
171: || fp == StandardNames.XSI_SCHEMA_LOCATION
172: || fp == StandardNames.XSI_NIL
173: || fp == StandardNames.XSI_NO_NAMESPACE_SCHEMA_LOCATION) {
174: return;
175: }
176: if (parentType instanceof SimpleType) {
177: StaticError err = new StaticError(
178: "Attribute "
179: + env.getNamePool()
180: .getDisplayName(nameCode)
181: + " is not permitted in the content model of the simple type "
182: + parentType.getDescription());
183: err.setIsTypeError(true);
184: err.setLocator(this );
185: throw err;
186: }
187: SchemaType type;
188: try {
189: type = ((ComplexType) parentType).getAttributeUseType(fp);
190: } catch (SchemaException e) {
191: throw new StaticError(e);
192: }
193: if (type == null) {
194: StaticError err = new StaticError(
195: "Attribute "
196: + env.getNamePool()
197: .getDisplayName(nameCode)
198: + " is not permitted in the content model of the complex type "
199: + parentType.getDescription());
200: err.setIsTypeError(true);
201: err.setLocator(this );
202: throw err;
203: }
204: if (type instanceof AnyType) {
205: return;
206: }
207:
208: try {
209: select.checkPermittedContents(type, env, true);
210: // TODO: does this allow for the fact that the content will be atomized?
211: } catch (XPathException e) {
212: if (e.getLocator() == null || e.getLocator() == e) {
213: e.setLocator(this );
214: }
215: throw e;
216: }
217: }
218:
219: /**
220: * Process this instruction
221: * @param context the dynamic context of the transformation
222: * @return a TailCall to be executed by the caller, always null for this instruction
223: */
224:
225: public TailCall processLeavingTail(XPathContext context)
226: throws XPathException {
227: Controller controller = context.getController();
228: SequenceReceiver out = context.getReceiver();
229: int opt = options;
230: int ann = annotation;
231:
232: // we may need to change the namespace prefix if the one we chose is
233: // already in use with a different namespace URI: this is done behind the scenes
234: // by the Outputter
235:
236: String value = expandChildren(context).toString();
237: if (schemaType != null) {
238: // test whether the value actually conforms to the given type
239: XPathException err = schemaType.validateContent(value,
240: DummyNamespaceResolver.getInstance(), context
241: .getConfiguration().getNameChecker());
242: if (err != null) {
243: ValidationException verr = new ValidationException(
244: "Attribute value "
245: + Err.wrap(value, Err.VALUE)
246: + " does not the match the required type "
247: + schemaType.getDescription() + ". "
248: + err.getMessage());
249: verr.setLocator(this );
250: throw verr;
251: }
252: } else if (validationAction == Validation.STRICT
253: || validationAction == Validation.LAX) {
254: try {
255: ann = controller.getConfiguration().validateAttribute(
256: nameCode, value, validationAction);
257: } catch (ValidationException e) {
258: DynamicError err = DynamicError.makeDynamicError(e);
259: err.setErrorCode(e.getErrorCodeLocalPart());
260: err.setXPathContext(context);
261: err.setLocator(this );
262: err.setIsTypeError(true);
263: throw err;
264: }
265: }
266: try {
267: out.attribute(nameCode, ann, value, locationId, opt);
268: } catch (XPathException err) {
269: throw dynamicError(this , err, context);
270: }
271:
272: return null;
273: }
274:
275: public Item evaluateItem(XPathContext context)
276: throws XPathException {
277: Orphan o = (Orphan) super .evaluateItem(context);
278: if (schemaType != null) {
279: XPathException err = schemaType.validateContent(o
280: .getStringValueCS(), DummyNamespaceResolver
281: .getInstance(), context.getConfiguration()
282: .getNameChecker());
283: if (err != null) {
284: throw new ValidationException("Attribute value "
285: + Err.wrap(o.getStringValueCS(), Err.VALUE)
286: + " does not the match the required type "
287: + schemaType.getDescription() + ". "
288: + err.getMessage());
289: }
290: o.setTypeAnnotation(schemaType.getFingerprint());
291: if (schemaType.isNamespaceSensitive()) {
292: throw new DynamicError(
293: "Cannot validate a parentless attribute whose content is namespace-sensitive");
294: }
295: } else if (validationAction == Validation.STRICT
296: || validationAction == Validation.LAX) {
297: try {
298: int ann = context.getController().getConfiguration()
299: .validateAttribute(nameCode,
300: o.getStringValueCS(), validationAction);
301: o.setTypeAnnotation(ann);
302: } catch (ValidationException e) {
303: DynamicError err = DynamicError.makeDynamicError(e);
304: err.setErrorCode(e.getErrorCodeLocalPart());
305: err.setXPathContext(context);
306: err.setLocator(this );
307: err.setIsTypeError(true);
308: throw err;
309: }
310: }
311:
312: return o;
313: }
314:
315: /**
316: * Display this instruction as an expression, for diagnostics
317: */
318:
319: public void display(int level, NamePool pool, PrintStream out) {
320: out.println(ExpressionTool.indent(level) + "attribute ");
321: out.println(ExpressionTool.indent(level + 1)
322: + "name "
323: + (pool == null ? nameCode + "" : pool
324: .getDisplayName(nameCode)));
325: super .display(level + 1, pool, out);
326: }
327: }
328:
329: //
330: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
331: // you may not use this file except in compliance with the License. You may obtain a copy of the
332: // License at http://www.mozilla.org/MPL/
333: //
334: // Software distributed under the License is distributed on an "AS IS" basis,
335: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
336: // See the License for the specific language governing rights and limitations under the License.
337: //
338: // The Original Code is: all this file.
339: //
340: // The Initial Developer of the Original Code is Michael H. Kay.
341: //
342: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
343: //
344: // Contributor(s): none.
345: //
|