001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.expr.ExpressionLocation;
004: import net.sf.saxon.om.*;
005: import net.sf.saxon.trans.DynamicError;
006: import net.sf.saxon.trans.XPathException;
007: import net.sf.saxon.type.Type;
008: import net.sf.saxon.value.AtomicValue;
009: import net.sf.saxon.Configuration;
010: import net.sf.saxon.Err;
011:
012: /**
013: * This class is used for generating complex content, that is, the content of an
014: * element or document node. It enforces the rules on the order of events within
015: * complex content (attributes and namespaces must come first), and it implements
016: * part of the namespace fixup rules, in particular, it ensures that there is a
017: * namespace node for the namespace used in the element name and in each attribute
018: * name.
019: *
020: * <p>The same ComplexContentOutputter may be used for generating an entire XML
021: * document; it is not necessary to create a new outputter for each element node.</p>
022: *
023: * @author Michael H. Kay
024: */
025:
026: public final class ComplexContentOutputter extends SequenceReceiver {
027:
028: private NamePool pool;
029: private Receiver receiver;
030: // the next receiver in the output pipeline
031:
032: private int pendingStartTag = -2;
033: // -2 means we are at the top level, or immediately within a document node
034: // -1 means we are in the content of an element node whose start tag is complete
035: private int level = -1; // records the number of startDocument or startElement events
036: // that have not yet been closed. Note that startDocument and startElement
037: // events may be arbitrarily nested; startDocument and endDocument
038: // are ignore unless they occur at the outermost level.
039: private Boolean elementIsInNullNamespace;
040: private int[] pendingAttCode = new int[20];
041: private int[] pendingAttType = new int[20];
042: private CharSequence[] pendingAttValue = new String[20];
043: private int[] pendingAttLocation = new int[20];
044: private int[] pendingAttProp = new int[20];
045: private int pendingAttListSize = 0;
046:
047: private int[] pendingNSList = new int[20];
048: private int pendingNSListSize = 0;
049:
050: private int currentSimpleType = -1; // any other value means we are currently writing an
051: // element of a particular simple type
052:
053: private int startElementProperties;
054: private int startElementLocationId;
055: private boolean declaresDefaultNamespace;
056: private boolean allowDuplicateAttributes;
057:
058: public ComplexContentOutputter() {
059: }
060:
061: public void setPipelineConfiguration(
062: PipelineConfiguration pipelineConfiguration) {
063: super .setPipelineConfiguration(pipelineConfiguration);
064: allowDuplicateAttributes = pipelineConfiguration
065: .getController().getExecutable().getHostLanguage() == Configuration.XSLT;
066: }
067:
068: public NamePool getNamePool() {
069: if (pool == null) {
070: pool = super .getNamePool();
071: }
072: return pool;
073: }
074:
075: public void setSystemId(String systemId) {
076: }
077:
078: public String getSystemId() {
079: return null;
080: }
081:
082: /**
083: * Set the receiver (to handle the next stage in the pipeline) directly
084: */
085:
086: public void setReceiver(Receiver receiver) {
087: this .receiver = receiver;
088: }
089:
090: /**
091: * Start the output process
092: */
093:
094: public void open() throws XPathException {
095: receiver.open();
096: previousAtomic = false;
097: }
098:
099: /**
100: * Start of a document node.
101: */
102:
103: public void startDocument(int properties) throws XPathException {
104: level++;
105: if (level == 0) {
106: receiver.startDocument(properties);
107: } else if (pendingStartTag >= 0) {
108: startContent();
109: pendingStartTag = -2;
110: }
111: previousAtomic = false;
112: }
113:
114: /**
115: * Notify the end of a document node
116: */
117:
118: public void endDocument() throws XPathException {
119: if (level == 0) {
120: receiver.endDocument();
121: }
122: level--;
123: }
124:
125: /**
126: * Produce text content output. <BR>
127: * Special characters are escaped using XML/HTML conventions if the output format
128: * requires it.
129: * @param s The String to be output
130: * @exception XPathException for any failure
131: */
132:
133: public void characters(CharSequence s, int locationId,
134: int properties) throws XPathException {
135: previousAtomic = false;
136: if (s == null)
137: return;
138: int len = s.length();
139: if (len == 0)
140: return;
141: if (pendingStartTag >= 0) {
142: startContent();
143: }
144: receiver.characters(s, locationId, properties);
145: }
146:
147: /**
148: * Output an element start tag. <br>
149: * The actual output of the tag is deferred until all attributes have been output
150: * using attribute().
151: * @param nameCode The element name code
152: */
153:
154: public void startElement(int nameCode, int typeCode,
155: int locationId, int properties) throws XPathException {
156: // System.err.println("StartElement " + nameCode);
157: level++;
158:
159: if (pendingStartTag >= 0) {
160: startContent();
161: }
162: startElementProperties = properties;
163: startElementLocationId = locationId;
164: pendingAttListSize = 0;
165: pendingNSListSize = 0;
166: pendingStartTag = nameCode;
167: elementIsInNullNamespace = null; // meaning not yet computed
168: currentSimpleType = typeCode;
169: previousAtomic = false;
170: }
171:
172: /**
173: * Output a namespace declaration. <br>
174: * This is added to a list of pending namespaces for the current start tag.
175: * If there is already another declaration of the same prefix, this one is
176: * ignored, unless the REJECT_DUPLICATES flag is set, in which case this is an error.
177: * Note that unlike SAX2 startPrefixMapping(), this call is made AFTER writing the start tag.
178: * @param nscode The namespace code
179: * @throws XPathException if there is no start tag to write to (created using writeStartTag),
180: * or if character content has been written since the start tag was written.
181: */
182:
183: public void namespace(int nscode, int properties)
184: throws XPathException {
185:
186: // System.err.println("Write namespace prefix=" + (nscode>>16) + " uri=" + (nscode&0xffff));
187: if (pendingStartTag < 0) {
188: throw NoOpenStartTagException.makeNoOpenStartTagException(
189: Type.NAMESPACE, getNamePool()
190: .getPrefixFromNamespaceCode(nscode),
191: getPipelineConfiguration().getConfiguration()
192: .getHostLanguage(),
193: (level <= 0 || pendingStartTag == -2),
194: getPipelineConfiguration().isSerializing()
195: && level <= 0);
196: }
197:
198: // elimination of namespaces already present on an outer element of the
199: // result tree is now done by the NamespaceReducer.
200:
201: // Handle declarations whose prefix is duplicated for this element.
202:
203: boolean rejectDuplicates = (properties & ReceiverOptions.REJECT_DUPLICATES) != 0;
204:
205: for (int i = 0; i < pendingNSListSize; i++) {
206: if (nscode == pendingNSList[i]) {
207: // same prefix and URI: ignore this duplicate
208: return;
209: }
210: if ((nscode >> 16) == (pendingNSList[i] >> 16)) {
211: if (rejectDuplicates) {
212: DynamicError err = new DynamicError(
213: "Cannot create two namespace nodes with the same name");
214: err.setErrorCode("XTDE0430");
215: throw err;
216: } else {
217: // same prefix, do a quick exit
218: return;
219: }
220: }
221: }
222:
223: // It is an error to output a namespace node for the default namespace if the element
224: // itself is in the null namespace, as the resulting element could not be serialized
225:
226: if (((nscode >> 16) == 0) && ((nscode & 0xffff) != 0)) {
227: declaresDefaultNamespace = true;
228: if (elementIsInNullNamespace == null) {
229: elementIsInNullNamespace = Boolean
230: .valueOf(getNamePool().getURI(pendingStartTag) == NamespaceConstant.NULL);
231: }
232: if (elementIsInNullNamespace.booleanValue()) {
233: DynamicError err = new DynamicError(
234: "Cannot output a namespace node for the default namespace when the element is in no namespace");
235: err.setErrorCode("XTDE0440");
236: throw err;
237: }
238: }
239:
240: // if it's not a duplicate namespace, add it to the list for this start tag
241:
242: if (pendingNSListSize + 1 > pendingNSList.length) {
243: int[] newlist = new int[pendingNSListSize * 2];
244: System.arraycopy(pendingNSList, 0, newlist, 0,
245: pendingNSListSize);
246: pendingNSList = newlist;
247: }
248: pendingNSList[pendingNSListSize++] = nscode;
249: previousAtomic = false;
250: }
251:
252: /**
253: * Output an attribute value. <br>
254: * This is added to a list of pending attributes for the current start tag, overwriting
255: * any previous attribute with the same name. <br>
256: * This method should NOT be used to output namespace declarations.<br>
257: * @param nameCode The name of the attribute
258: * @param value The value of the attribute
259: * @param properties Bit fields containing properties of the attribute to be written
260: * @throws XPathException if there is no start tag to write to (created using writeStartTag),
261: * or if character content has been written since the start tag was written.
262: */
263:
264: public void attribute(int nameCode, int typeCode,
265: CharSequence value, int locationId, int properties)
266: throws XPathException {
267: //System.err.println("Write attribute " + nameCode + "=" + value + " to Outputter " + this);
268:
269: if (pendingStartTag < 0) {
270: // The complexity here is in identifying the right error message and error code
271: DynamicError err = NoOpenStartTagException
272: .makeNoOpenStartTagException(Type.ATTRIBUTE,
273: getNamePool().getDisplayName(nameCode),
274: getPipelineConfiguration()
275: .getConfiguration()
276: .getHostLanguage(),
277: (level <= 0 || pendingStartTag == -2),
278: getPipelineConfiguration().isSerializing()
279: && level <= 0);
280: err.setLocator(new ExpressionLocation(
281: getPipelineConfiguration().getLocationProvider(),
282: locationId));
283: throw err;
284: }
285:
286: // if this is a duplicate attribute, overwrite the original, unless
287: // the REJECT_DUPLICATES option is set.
288:
289: for (int a = 0; a < pendingAttListSize; a++) {
290: if ((pendingAttCode[a] & 0xfffff) == (nameCode & 0xfffff)) {
291: if (allowDuplicateAttributes) {
292: pendingAttType[a] = typeCode;
293: pendingAttValue[a] = value;
294: pendingAttLocation[a] = locationId;
295: pendingAttProp[a] = properties;
296: return;
297: } else {
298: DynamicError err = new DynamicError(
299: "Cannot create an element having two attributes with the same name: "
300: + Err.wrap(getNamePool()
301: .getDisplayName(nameCode),
302: Err.ATTRIBUTE));
303: err.setErrorCode("XQDY0025");
304: throw err;
305: }
306: }
307: }
308:
309: // otherwise, add this one to the list
310:
311: if (pendingAttListSize >= pendingAttCode.length) {
312: int[] attCode2 = new int[pendingAttListSize * 2];
313: int[] attType2 = new int[pendingAttListSize * 2];
314: String[] attValue2 = new String[pendingAttListSize * 2];
315: int[] attLoc2 = new int[pendingAttListSize * 2];
316: int[] attProp2 = new int[pendingAttListSize * 2];
317: System.arraycopy(pendingAttCode, 0, attCode2, 0,
318: pendingAttListSize);
319: System.arraycopy(pendingAttType, 0, attType2, 0,
320: pendingAttListSize);
321: System.arraycopy(pendingAttValue, 0, attValue2, 0,
322: pendingAttListSize);
323: System.arraycopy(pendingAttLocation, 0, attLoc2, 0,
324: pendingAttListSize);
325: System.arraycopy(pendingAttProp, 0, attProp2, 0,
326: pendingAttListSize);
327: pendingAttCode = attCode2;
328: pendingAttType = attType2;
329: pendingAttValue = attValue2;
330: pendingAttLocation = attLoc2;
331: pendingAttProp = attProp2;
332: }
333:
334: pendingAttCode[pendingAttListSize] = nameCode;
335: pendingAttType[pendingAttListSize] = typeCode;
336: pendingAttValue[pendingAttListSize] = value;
337: pendingAttLocation[pendingAttListSize] = locationId;
338: pendingAttProp[pendingAttListSize] = properties;
339: pendingAttListSize++;
340: previousAtomic = false;
341: }
342:
343: /**
344: * Check that the prefix for an element or attribute is acceptable, allocating a substitute
345: * prefix if not. The prefix is acceptable unless a namespace declaration has been
346: * written that assignes this prefix to a different namespace URI. This method
347: * also checks that the element or attribute namespace has been declared, and declares it
348: * if not.
349: */
350:
351: private int checkProposedPrefix(int nameCode, int seq)
352: throws XPathException {
353: NamePool namePool = getNamePool();
354: int nscode = namePool.getNamespaceCode(nameCode);
355: if (nscode == -1) {
356: // avoid calling allocate where possible, because it's synchronized
357: nscode = namePool.allocateNamespaceCode(nameCode);
358: }
359: int nsprefix = nscode >> 16;
360:
361: for (int i = 0; i < pendingNSListSize; i++) {
362: if (nsprefix == (pendingNSList[i] >> 16)) {
363: // same prefix
364: if ((nscode & 0xffff) == (pendingNSList[i] & 0xffff)) {
365: // same URI
366: return nameCode; // all is well
367: } else {
368: String prefix = getSubstitutePrefix(nscode, seq);
369:
370: int newCode = namePool.allocate(prefix, namePool
371: .getURI(nameCode), namePool
372: .getLocalName(nameCode));
373: namespace(namePool.allocateNamespaceCode(newCode),
374: 0);
375: return newCode;
376: }
377: }
378: }
379: // no declaration of this prefix: declare it now
380: namespace(nscode, 0);
381: return nameCode;
382: }
383:
384: /**
385: * It is possible for a single output element to use the same prefix to refer to different
386: * namespaces. In this case we have to generate an alternative prefix for uniqueness. The
387: * one we generate is based on the sequential position of the element/attribute: this is
388: * designed to ensure both uniqueness (with a high probability) and repeatability
389: */
390:
391: private String getSubstitutePrefix(int nscode, int seq) {
392: String prefix = getNamePool()
393: .getPrefixFromNamespaceCode(nscode);
394: return prefix + '_' + seq;
395: }
396:
397: /**
398: * Output an element end tag.
399: */
400:
401: public void endElement() throws XPathException {
402: //System.err.println("Write end tag " + this + " : " + name);
403: if (pendingStartTag >= 0) {
404: startContent();
405: }
406:
407: // write the end tag
408:
409: receiver.endElement();
410: level--;
411: previousAtomic = false;
412: }
413:
414: /**
415: * Write a comment
416: */
417:
418: public void comment(CharSequence comment, int locationId,
419: int properties) throws XPathException {
420: if (pendingStartTag >= 0) {
421: startContent();
422: }
423: receiver.comment(comment, locationId, properties);
424: previousAtomic = false;
425: }
426:
427: /**
428: * Write a processing instruction
429: */
430:
431: public void processingInstruction(String target, CharSequence data,
432: int locationId, int properties) throws XPathException {
433: if (pendingStartTag >= 0) {
434: startContent();
435: }
436: receiver.processingInstruction(target, data, locationId,
437: properties);
438: previousAtomic = false;
439: }
440:
441: /**
442: * Append an arbitrary item (node or atomic value) to the output
443: * @param item the item to be appended
444: * @param locationId the location of the calling instruction, for diagnostics
445: * @param copyNamespaces if the item is an element node, this indicates whether its namespaces
446: */
447:
448: public void append(Item item, int locationId, int copyNamespaces)
449: throws XPathException {
450: if (item == null) {
451: return;
452: } else if (item instanceof AtomicValue) {
453: if (previousAtomic) {
454: characters(" ", locationId, 0);
455: }
456: characters(item.getStringValueCS(), locationId, 0);
457: previousAtomic = true;
458: } else if (((NodeInfo) item).getNodeKind() == Type.DOCUMENT) {
459: SequenceIterator iter = ((NodeInfo) item)
460: .iterateAxis(Axis.CHILD);
461: while (true) {
462: Item it = iter.next();
463: if (it == null)
464: break;
465: append(it, locationId, copyNamespaces);
466: }
467: } else {
468: ((NodeInfo) item).copy(this , copyNamespaces, true,
469: locationId);
470: previousAtomic = false;
471: }
472: }
473:
474: /**
475: * Close the output
476: */
477:
478: public void close() throws XPathException {
479: // System.err.println("Close " + this + " using emitter " + emitter.getClass());
480: receiver.close();
481: previousAtomic = false;
482: }
483:
484: /**
485: * Flush out a pending start tag
486: */
487:
488: public void startContent() throws XPathException {
489:
490: if (pendingStartTag < 0) {
491: // this can happen if the method is called from outside,
492: // e.g. from a SequenceOutputter earlier in the pipeline
493: return;
494: }
495:
496: int props = startElementProperties;
497: int elcode = pendingStartTag;
498: if (declaresDefaultNamespace || (elcode >> 20 & 0xff) != 0) {
499: // skip this check if the element is unprefixed and no xmlns="abc" declaration has been encountered
500: elcode = checkProposedPrefix(pendingStartTag, 0);
501: props = startElementProperties
502: | ReceiverOptions.NAMESPACE_OK;
503: }
504: receiver.startElement(elcode, currentSimpleType,
505: startElementLocationId, props);
506:
507: for (int a = 0; a < pendingAttListSize; a++) {
508: int attcode = pendingAttCode[a];
509: if ((attcode >> 20 & 0xff) != 0) { // non-null prefix
510: pendingAttCode[a] = checkProposedPrefix(attcode, a + 1);
511: }
512: }
513:
514: for (int n = 0; n < pendingNSListSize; n++) {
515: receiver.namespace(pendingNSList[n], 0);
516: }
517:
518: for (int a = 0; a < pendingAttListSize; a++) {
519: receiver.attribute(pendingAttCode[a], pendingAttType[a],
520: pendingAttValue[a], pendingAttLocation[a],
521: pendingAttProp[a]);
522: }
523:
524: receiver.startContent();
525:
526: pendingAttListSize = 0;
527: pendingNSListSize = 0;
528: pendingStartTag = -1;
529: previousAtomic = false;
530: }
531:
532: }
533:
534: //
535: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
536: // you may not use this file except in compliance with the License. You may obtain a copy of the
537: // License at http://www.mozilla.org/MPL/
538: //
539: // Software distributed under the License is distributed on an "AS IS" basis,
540: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
541: // See the License for the specific language governing rights and limitations under the License.
542: //
543: // The Original Code is: all this file.
544: //
545: // The Initial Developer of the Original Code is Michael H. Kay.
546: //
547: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
548: //
549: // Contributor(s): none.
550: //
|