001: package net.sf.saxon.instruct;
002:
003: import net.sf.saxon.Controller;
004: import net.sf.saxon.Configuration;
005: import net.sf.saxon.pattern.NodeKindTest;
006: import net.sf.saxon.pattern.ContentTypeTest;
007: import net.sf.saxon.event.*;
008: import net.sf.saxon.expr.*;
009: import net.sf.saxon.om.*;
010: import net.sf.saxon.style.StandardNames;
011: import net.sf.saxon.trans.DynamicError;
012: import net.sf.saxon.trans.XPathException;
013: import net.sf.saxon.type.*;
014: import net.sf.saxon.value.AtomicValue;
015: import net.sf.saxon.value.Value;
016: import net.sf.saxon.value.Whitespace;
017:
018: import java.io.PrintStream;
019: import java.util.Iterator;
020:
021: /**
022: * An xsl:copy-of element in the stylesheet.
023: */
024:
025: public class CopyOf extends Instruction implements MappingFunction {
026:
027: private Expression select;
028: private boolean copyNamespaces;
029: private int validation;
030: private SchemaType schemaType;
031: private boolean requireDocumentOrElement = false;
032: private boolean rejectDuplicateAttributes;
033: private boolean readOnce = false;
034:
035: public CopyOf(Expression select, boolean copyNamespaces,
036: int validation, SchemaType schemaType,
037: boolean rejectDuplicatAttributes) {
038: this .select = select;
039: this .copyNamespaces = copyNamespaces;
040: this .validation = validation;
041: this .schemaType = schemaType;
042: this .rejectDuplicateAttributes = rejectDuplicatAttributes;
043: adoptChildExpression(select);
044: }
045:
046: public void setReadOnce(boolean b) {
047: readOnce = b;
048: }
049:
050: /**
051: * Determine whether this instruction creates new nodes.
052: * The result depends on the type of the select expression.
053: */
054:
055: public final boolean createsNewNodes() {
056: final TypeHierarchy th = getExecutable().getConfiguration()
057: .getNamePool().getTypeHierarchy();
058: return !select.getItemType(th).isAtomicType();
059: }
060:
061: /**
062: * Get the name of this instruction, for diagnostics and tracing
063: */
064:
065: public int getInstructionNameCode() {
066: return StandardNames.XSL_COPY_OF;
067: }
068:
069: /**
070: * For XQuery, the operand (select) must be a single element or document node.
071: * @param requireDocumentOrElement
072: */
073: public void setRequireDocumentOrElement(
074: boolean requireDocumentOrElement) {
075: this .requireDocumentOrElement = requireDocumentOrElement;
076: }
077:
078: /**
079: * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process().
080: * This method indicates which of these methods is provided. This implementation provides both iterate() and
081: * process() methods natively.
082: */
083:
084: public int getImplementationMethod() {
085: return ITERATE_METHOD | PROCESS_METHOD;
086: }
087:
088: /**
089: * Process this xsl:copy-of instruction
090: * @param context the dynamic context for the transformation
091: * @return null - this implementation of the method never returns a TailCall
092: */
093:
094: public TailCall processLeavingTail(XPathContext context)
095: throws XPathException {
096:
097: Controller controller = context.getController();
098: SequenceReceiver out = context.getReceiver();
099:
100: int whichNamespaces = (copyNamespaces ? NodeInfo.ALL_NAMESPACES
101: : NodeInfo.NO_NAMESPACES);
102:
103: SequenceIterator iter = select.iterate(context);
104: while (true) {
105:
106: Item item = iter.next();
107: if (item == null) {
108: break;
109: }
110: if (item instanceof NodeInfo) {
111: NodeInfo source = (NodeInfo) item;
112: int kind = source.getNodeKind();
113: if (requireDocumentOrElement
114: && !(kind == Type.ELEMENT || kind == Type.DOCUMENT)) {
115: DynamicError e = new DynamicError(
116: "Operand of validate expression must be a document or element node");
117: e.setXPathContext(context);
118: e.setErrorCode("XQTY0030");
119: throw e;
120: }
121: switch (kind) {
122:
123: case Type.ELEMENT:
124:
125: Receiver eval = controller.getConfiguration()
126: .getElementValidator(out,
127: source.getNameCode(), locationId,
128: schemaType, validation,
129: controller.getNamePool());
130: source
131: .copy(eval, whichNamespaces, true,
132: locationId);
133: break;
134:
135: case Type.ATTRIBUTE:
136: try {
137: copyAttribute(source, schemaType, validation,
138: this , context,
139: rejectDuplicateAttributes);
140: } catch (NoOpenStartTagException err) {
141: DynamicError e = new DynamicError(err
142: .getMessage());
143: e.setLocator(this );
144: e.setXPathContext(context);
145: e.setErrorCode(err.getErrorCodeLocalPart());
146: throw dynamicError(this , e, context);
147: }
148: break;
149: case Type.TEXT:
150: out.characters(source.getStringValueCS(),
151: locationId, 0);
152: break;
153:
154: case Type.PROCESSING_INSTRUCTION:
155: out.processingInstruction(source.getDisplayName(),
156: source.getStringValueCS(), locationId, 0);
157: break;
158:
159: case Type.COMMENT:
160: out.comment(source.getStringValueCS(), locationId,
161: 0);
162: break;
163:
164: case Type.NAMESPACE:
165: try {
166: source.copy(out, NodeInfo.NO_NAMESPACES, false,
167: locationId);
168: } catch (NoOpenStartTagException err) {
169: DynamicError e = new DynamicError(err
170: .getMessage());
171: e.setXPathContext(context);
172: e.setErrorCode(err.getErrorCodeLocalPart());
173: //context.getController().recoverableError(e);
174: throw dynamicError(this , e, context);
175: }
176: break;
177:
178: case Type.DOCUMENT:
179: Receiver val = controller.getConfiguration()
180: .getDocumentValidator(out,
181: source.getBaseURI(),
182: controller.getNamePool(),
183: validation, Whitespace.NONE,
184: schemaType);
185: val.setPipelineConfiguration(out
186: .getPipelineConfiguration());
187: source.copy(val, whichNamespaces, true, locationId);
188: break;
189:
190: default:
191: throw new IllegalArgumentException(
192: "Unknown node kind " + source.getNodeKind());
193: }
194:
195: } else {
196: out.append(item, locationId, NodeInfo.ALL_NAMESPACES);
197: }
198: }
199: return null;
200: }
201:
202: protected static void copyAttribute(NodeInfo source,
203: SchemaType schemaType, int validation,
204: Instruction instruction, XPathContext context,
205: boolean rejectDuplicates) throws XPathException {
206: int nameCode = source.getNameCode();
207: int annotation = -1;
208: int opt = 0;
209: if (rejectDuplicates) {
210: opt |= ReceiverOptions.REJECT_DUPLICATES;
211: }
212: CharSequence value = source.getStringValueCS();
213: if (schemaType != null) {
214: if (schemaType.isSimpleType()) {
215: if (((SimpleType) schemaType).isNamespaceSensitive()) {
216: DynamicError err = new DynamicError(
217: "Cannot create a parentless attribute whose "
218: + "type is namespace-sensitive (such as xs:QName)");
219: err.setErrorCode("XTTE1545");
220: err.setXPathContext(context);
221: err.setLocator(instruction);
222: throw err;
223: }
224: try {
225: XPathException err = ((SimpleType) schemaType)
226: .validateContent(value,
227: DummyNamespaceResolver
228: .getInstance(), context
229: .getConfiguration()
230: .getNameChecker());
231: if (err != null) {
232: throw new ValidationException(
233: "Attribute being copied does not match the required type. "
234: + err.getMessage());
235: }
236: annotation = schemaType.getFingerprint();
237: } catch (UnresolvedReferenceException ure) {
238: throw new ValidationException(ure);
239: }
240: } else {
241: DynamicError e = new DynamicError(
242: "Cannot validate an attribute against a complex type");
243: e.setXPathContext(context);
244: e.setErrorCode("XTSE1530");
245: throw e;
246: }
247: } else if (validation == Validation.STRICT
248: || validation == Validation.LAX) {
249: try {
250: annotation = context.getController().getConfiguration()
251: .validateAttribute(nameCode, value, validation);
252: } catch (ValidationException e) {
253: DynamicError err = DynamicError.makeDynamicError(e);
254: err.setErrorCode(e.getErrorCodeLocalPart());
255: err.setXPathContext(context);
256: err.setLocator(instruction);
257: err.setIsTypeError(true);
258: throw err;
259: }
260:
261: } else if (validation == Validation.PRESERVE) {
262: annotation = source.getTypeAnnotation();
263: }
264:
265: context.getReceiver().attribute(nameCode, annotation, value,
266: instruction.getLocationId(), opt);
267: }
268:
269: public Expression simplify(StaticContext env) throws XPathException {
270: select = select.simplify(env);
271: return this ;
272: }
273:
274: public ItemType getItemType(TypeHierarchy th) {
275: if (schemaType != null) {
276: Configuration config = getExecutable().getConfiguration();
277: ItemType in = select.getItemType(th);
278: int e = th.relationship(in, NodeKindTest.ELEMENT);
279: if (e == TypeHierarchy.SAME_TYPE
280: || e == TypeHierarchy.SUBSUMED_BY) {
281: return new ContentTypeTest(Type.ELEMENT, schemaType,
282: config);
283: }
284: int a = th.relationship(in, NodeKindTest.ATTRIBUTE);
285: if (a == TypeHierarchy.SAME_TYPE
286: || a == TypeHierarchy.SUBSUMED_BY) {
287: return new ContentTypeTest(Type.ATTRIBUTE, schemaType,
288: config);
289: }
290: }
291: return select.getItemType(th);
292: }
293:
294: public int getCardinality() {
295: return select.getCardinality();
296: }
297:
298: public int getDependencies() {
299: return select.getDependencies();
300: }
301:
302: protected void promoteInst(PromotionOffer offer)
303: throws XPathException {
304: select = doPromotion(select, offer);
305: }
306:
307: public Expression typeCheck(StaticContext env,
308: ItemType contextItemType) throws XPathException {
309: select = select.typeCheck(env, contextItemType);
310: adoptChildExpression(select);
311: return this ;
312: }
313:
314: public Expression optimize(Optimizer opt, StaticContext env,
315: ItemType contextItemType) throws XPathException {
316: select = select.optimize(opt, env, contextItemType);
317: adoptChildExpression(select);
318: if (readOnce) {
319: Expression optcopy = opt.optimizeCopy(select);
320: if (optcopy != null) {
321: return optcopy;
322: }
323: }
324: return this ;
325: }
326:
327: /**
328: * Diagnostic print of expression structure. The expression is written to the System.err
329: * output stream
330: *
331: * @param level indentation level for this expression
332: * @param out
333: */
334:
335: public void display(int level, NamePool pool, PrintStream out) {
336: out.println(ExpressionTool.indent(level) + "copyOf "
337: + ("validation=" + Validation.toString(validation)));
338: select.display(level + 1, pool, out);
339: }
340:
341: public Iterator iterateSubExpressions() {
342: return new MonoIterator(select);
343: }
344:
345: /**
346: * Return the first item if there is one, or null if not
347: * @param context
348: * @return the result of evaluating the instruction
349: * @throws XPathException
350: */
351:
352: public Item evaluateItem(XPathContext context)
353: throws XPathException {
354: return super .evaluateItem(context);
355: }
356:
357: public SequenceIterator iterate(XPathContext context)
358: throws XPathException {
359: if (validation == Validation.PRESERVE && schemaType == null
360: && copyNamespaces) {
361: // create a virtual copy of the underlying nodes
362: return new MappingIterator(select.iterate(context), this ,
363: context);
364: }
365: Controller controller = context.getController();
366: XPathContext c2 = context.newMinorContext();
367: c2.setOrigin(this );
368: SequenceOutputter out = new SequenceOutputter();
369: out.setPipelineConfiguration(controller
370: .makePipelineConfiguration());
371: c2.setReceiver(out);
372: try {
373: process(c2);
374: return Value.getIterator(out.getSequence());
375: } catch (XPathException err) {
376: if (err instanceof ValidationException) {
377: ((ValidationException) err).setSourceLocator(this );
378: ((ValidationException) err).setSystemId(getSystemId());
379: }
380: if (err.getLocator() == null) {
381: err.setLocator(this );
382: }
383: throw err;
384: }
385: }
386:
387: /**
388: * Mapping function used to perform the copy when using the iterate() method
389: */
390:
391: /**
392: * Map one item to a sequence.
393: *
394: * @param item The item to be mapped.
395: * If context is supplied, this must be the same as context.currentItem().
396: * @param context The processing context. This is supplied only for mapping constructs that
397: * set the context node, position, and size. Otherwise it is null.
398: * @return either (a) a SequenceIterator over the sequence of items that the supplied input
399: * item maps to, or (b) an Item if it maps to a single item, or (c) null if it maps to an empty
400: * sequence.
401: */
402:
403: public Object map(Item item, XPathContext context)
404: throws XPathException {
405: if (item instanceof AtomicValue) {
406: return item;
407: }
408: VirtualCopy vc = VirtualCopy.makeVirtualCopy((NodeInfo) item,
409: (NodeInfo) item);
410: int documentNumber = context.getController().getConfiguration()
411: .getDocumentNumberAllocator().allocateDocumentNumber();
412: vc.setDocumentNumber(documentNumber);
413: return vc;
414: }
415:
416: }
417:
418: //
419: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
420: // you may not use this file except in compliance with the License. You may obtain a copy of the
421: // License at http://www.mozilla.org/MPL/
422: //
423: // Software distributed under the License is distributed on an "AS IS" basis,
424: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
425: // See the License for the specific language governing rights and limitations under the License.
426: //
427: // The Original Code is: all this file.
428: //
429: // The Initial Developer of the Original Code is Michael H. Kay.
430: //
431: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
432: //
433: // Contributor(s): none.
434: //
|