KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > saxon > event > ComplexContentOutputter


1 package net.sf.saxon.event;
2 import net.sf.saxon.expr.ExpressionLocation;
3 import net.sf.saxon.om.*;
4 import net.sf.saxon.trans.DynamicError;
5 import net.sf.saxon.trans.XPathException;
6 import net.sf.saxon.type.Type;
7 import net.sf.saxon.value.AtomicValue;
8 import net.sf.saxon.Configuration;
9 import net.sf.saxon.Err;
10
11 /**
12  * This class is used for generating complex content, that is, the content of an
13  * element or document node. It enforces the rules on the order of events within
14  * complex content (attributes and namespaces must come first), and it implements
15  * part of the namespace fixup rules, in particular, it ensures that there is a
16  * namespace node for the namespace used in the element name and in each attribute
17  * name.
18  *
19  * <p>The same ComplexContentOutputter may be used for generating an entire XML
20  * document; it is not necessary to create a new outputter for each element node.</p>
21  *
22  * @author Michael H. Kay
23  */

24
25
26
27 public final class ComplexContentOutputter extends SequenceReceiver {
28
29     private NamePool pool;
30     private Receiver receiver;
31             // the next receiver in the output pipeline
32

33     private int pendingStartTag = -2;
34             // -2 means we are at the top level, or immediately within a document node
35
// -1 means we are in the content of an element node whose start tag is complete
36
private int level = -1; // -1 means there is no open document;
37
// 0 there is an open document but no open element
38
// +n nested n levels deep in elements
39
private Boolean JavaDoc elementIsInNullNamespace;
40     private int[] pendingAttCode = new int[20];
41     private int[] pendingAttType = new int[20];
42     private CharSequence JavaDoc[] pendingAttValue = new String JavaDoc[20];
43     private int[] pendingAttLocation = new int[20];
44     private int[] pendingAttProp = new int[20];
45     private int pendingAttListSize = 0;
46
47     private int[] pendingNSList = new int[20];
48     private int pendingNSListSize = 0;
49
50     private int currentSimpleType = -1; // any other value means we are currently writing an
51
// element of a particular simple type
52

53     private int startElementProperties;
54     private int startElementLocationId;
55     private boolean declaresDefaultNamespace;
56     private boolean allowDuplicateAttributes;
57
58     public ComplexContentOutputter() {}
59
60     public void setPipelineConfiguration(PipelineConfiguration pipelineConfiguration) {
61         super.setPipelineConfiguration(pipelineConfiguration);
62         allowDuplicateAttributes =
63                 pipelineConfiguration.getController().getExecutable().getHostLanguage() == Configuration.XSLT;
64     }
65
66     public NamePool getNamePool() {
67         if (pool == null) {
68             pool = super.getNamePool();
69         }
70         return pool;
71     }
72
73     public void setSystemId(String JavaDoc systemId) {}
74
75     public String JavaDoc getSystemId() {
76         return null;
77     }
78
79     /**
80      * Set the receiver (to handle the next stage in the pipeline) directly
81      */

82
83     public void setReceiver(Receiver receiver) {
84         this.receiver = receiver;
85     }
86
87     /**
88      * Start the output process
89      */

90
91     public void open() throws XPathException {
92         receiver.open();
93         previousAtomic = false;
94     }
95
96     /**
97      * Start of a document node.
98     */

99
100     public void startDocument(int properties) throws XPathException {
101         if (level < 0) {
102             receiver.startDocument(properties);
103             level = 0;
104         } else if (pendingStartTag >= 0) {
105             startContent();
106             pendingStartTag = -2;
107         }
108         previousAtomic = false;
109     }
110
111     /**
112      * Notify the end of a document node
113      */

114
115     public void endDocument() throws XPathException {
116         if (level == 0) {
117             receiver.endDocument();
118             level = -1;
119         }
120     }
121
122
123     /**
124     * Produce text content output. <BR>
125     * Special characters are escaped using XML/HTML conventions if the output format
126     * requires it.
127     * @param s The String to be output
128     * @exception XPathException for any failure
129     */

130
131     public void characters(CharSequence JavaDoc s, int locationId, int properties) throws XPathException {
132         previousAtomic = false;
133         if (s==null) return;
134         int len = s.length();
135         if (len==0) return;
136         if (pendingStartTag >= 0) {
137             startContent();
138         }
139         receiver.characters(s, locationId, properties);
140     }
141
142     /**
143     * Output an element start tag. <br>
144     * The actual output of the tag is deferred until all attributes have been output
145     * using attribute().
146     * @param nameCode The element name code
147     */

148
149     public void startElement(int nameCode, int typeCode, int locationId, int properties) throws XPathException {
150         // System.err.println("StartElement " + nameCode);
151
level++;
152         if (level == 0) {
153             level = 1;
154         }
155
156         if (pendingStartTag >= 0) {
157             startContent();
158         }
159         startElementProperties = properties;
160         startElementLocationId = locationId;
161         pendingAttListSize = 0;
162         pendingNSListSize = 0;
163         pendingStartTag = nameCode;
164         elementIsInNullNamespace = null; // meaning not yet computed
165
currentSimpleType = typeCode;
166         previousAtomic = false;
167     }
168
169
170     /**
171     * Output a namespace declaration. <br>
172     * This is added to a list of pending namespaces for the current start tag.
173     * If there is already another declaration of the same prefix, this one is
174     * ignored, unless the REJECT_DUPLICATES flag is set, in which case this is an error.
175     * Note that unlike SAX2 startPrefixMapping(), this call is made AFTER writing the start tag.
176     * @param nscode The namespace code
177     * @throws XPathException if there is no start tag to write to (created using writeStartTag),
178     * or if character content has been written since the start tag was written.
179     */

180
181     public void namespace(int nscode, int properties)
182     throws XPathException {
183
184         // System.err.println("Write namespace prefix=" + (nscode>>16) + " uri=" + (nscode&0xffff));
185
if (pendingStartTag < 0) {
186             throw NoOpenStartTagException.makeNoOpenStartTagException(
187                     Type.NAMESPACE,
188                     getNamePool().getPrefix(nscode),
189                     getPipelineConfiguration().getConfiguration().getHostLanguage(),
190                     (level <= 0 || pendingStartTag == -2),
191                     getPipelineConfiguration().isSerializing() && level <= 0
192             );
193         }
194
195         // elimination of namespaces already present on an outer element of the
196
// result tree is now done by the NamespaceReducer.
197

198         // Handle declarations whose prefix is duplicated for this element.
199

200         boolean rejectDuplicates = (properties & ReceiverOptions.REJECT_DUPLICATES) != 0;
201
202         for (int i=0; i<pendingNSListSize; i++) {
203             if (nscode == pendingNSList[i]) {
204                 // same prefix and URI: ignore this duplicate
205
return;
206             }
207             if ((nscode>>16) == (pendingNSList[i]>>16)) {
208                 if (rejectDuplicates) {
209                     DynamicError err = new DynamicError("Cannot create two namespace nodes with the same name");
210                     err.setErrorCode("XTDE0430");
211                     throw err;
212                 } else {
213                     // same prefix, do a quick exit
214
return;
215                 }
216             }
217         }
218
219         // It is an error to output a namespace node for the default namespace if the element
220
// itself is in the null namespace, as the resulting element could not be serialized
221

222         if (((nscode>>16) == 0) && ((nscode&0xffff)!=0)) {
223             declaresDefaultNamespace = true;
224             if (elementIsInNullNamespace == null) {
225                 elementIsInNullNamespace = Boolean.valueOf(
226                         getNamePool().getURI(pendingStartTag) == NamespaceConstant.NULL);
227             }
228             if (elementIsInNullNamespace.booleanValue()) {
229                 DynamicError err = new DynamicError(
230                         "Cannot output a namespace node for the default namespace when the element is in no namespace");
231                 err.setErrorCode("XTDE0440");
232                 throw err;
233             }
234         }
235
236         // if it's not a duplicate namespace, add it to the list for this start tag
237

238         if (pendingNSListSize+1 > pendingNSList.length) {
239             int[] newlist = new int[pendingNSListSize * 2];
240             System.arraycopy(pendingNSList, 0, newlist, 0, pendingNSListSize);
241             pendingNSList = newlist;
242         }
243         pendingNSList[pendingNSListSize++] = nscode;
244         previousAtomic = false;
245     }
246
247
248     /**
249     * Output an attribute value. <br>
250     * This is added to a list of pending attributes for the current start tag, overwriting
251     * any previous attribute with the same name. <br>
252     * This method should NOT be used to output namespace declarations.<br>
253     * @param nameCode The name of the attribute
254     * @param value The value of the attribute
255     * @param properties Bit fields containing properties of the attribute to be written
256     * @throws XPathException if there is no start tag to write to (created using writeStartTag),
257     * or if character content has been written since the start tag was written.
258     */

259
260     public void attribute(int nameCode, int typeCode, CharSequence JavaDoc value, int locationId, int properties) throws XPathException {
261         //System.err.println("Write attribute " + nameCode + "=" + value + " to Outputter " + this);
262

263         if (pendingStartTag < 0) {
264             // The complexity here is in identifying the right error message and error code
265
DynamicError err = NoOpenStartTagException.makeNoOpenStartTagException(
266                     Type.ATTRIBUTE,
267                     getNamePool().getDisplayName(nameCode),
268                     getPipelineConfiguration().getConfiguration().getHostLanguage(),
269                     (level<=0 || pendingStartTag == -2),
270                     getPipelineConfiguration().isSerializing() && level <= 0);
271             err.setLocator(new ExpressionLocation(
272                     getPipelineConfiguration().getLocationProvider(),
273                     locationId));
274             throw err;
275         }
276
277         // if this is a duplicate attribute, overwrite the original, unless
278
// the REJECT_DUPLICATES option is set.
279

280         for (int a=0; a<pendingAttListSize; a++) {
281             if ((pendingAttCode[a] & 0xfffff) == (nameCode & 0xfffff)) {
282                 if (allowDuplicateAttributes) {
283                     pendingAttType[a] = typeCode;
284                     pendingAttValue[a] = value;
285                     pendingAttLocation[a] = locationId;
286                     pendingAttProp[a] = properties;
287                     return;
288                 } else {
289                     DynamicError err = new DynamicError("Cannot create an element having two attributes with the same name: " +
290                             Err.wrap(getNamePool().getDisplayName(nameCode), Err.ATTRIBUTE));
291                     err.setErrorCode("XQDY0025");
292                     throw err;
293                 }
294             }
295         }
296
297         // otherwise, add this one to the list
298

299         if (pendingAttListSize >= pendingAttCode.length) {
300             int[] attCode2 = new int[pendingAttListSize*2];
301             int[] attType2 = new int[pendingAttListSize*2];
302             String JavaDoc[] attValue2 = new String JavaDoc[pendingAttListSize*2];
303             int[] attLoc2 = new int[pendingAttListSize*2];
304             int[] attProp2 = new int[pendingAttListSize*2];
305             System.arraycopy(pendingAttCode, 0, attCode2, 0, pendingAttListSize);
306             System.arraycopy(pendingAttType, 0, attType2, 0, pendingAttListSize);
307             System.arraycopy(pendingAttValue, 0, attValue2, 0, pendingAttListSize);
308             System.arraycopy(pendingAttLocation, 0, attLoc2, 0, pendingAttListSize);
309             System.arraycopy(pendingAttProp, 0, attProp2, 0, pendingAttListSize);
310             pendingAttCode = attCode2;
311             pendingAttType = attType2;
312             pendingAttValue = attValue2;
313             pendingAttLocation = attLoc2;
314             pendingAttProp = attProp2;
315         }
316
317         pendingAttCode[pendingAttListSize] = nameCode;
318         pendingAttType[pendingAttListSize] = typeCode;
319         pendingAttValue[pendingAttListSize] = value;
320         pendingAttLocation[pendingAttListSize] = locationId;
321         pendingAttProp[pendingAttListSize] = properties;
322         pendingAttListSize++;
323         previousAtomic = false;
324     }
325
326     /**
327     * Check that the prefix for an element or attribute is acceptable, allocating a substitute
328     * prefix if not. The prefix is acceptable unless a namespace declaration has been
329     * written that assignes this prefix to a different namespace URI. This method
330     * also checks that the element or attribute namespace has been declared, and declares it
331     * if not.
332     */

333
334     private int checkProposedPrefix(int nameCode, int seq) throws XPathException {
335         NamePool namePool = getNamePool();
336         int nscode = namePool.getNamespaceCode(nameCode);
337         if (nscode == -1) {
338             // avoid calling allocate where possible, because it's synchronized
339
nscode = namePool.allocateNamespaceCode(nameCode);
340         }
341         int nsprefix = nscode>>16;
342
343         for (int i=0; i<pendingNSListSize; i++) {
344             if (nsprefix == (pendingNSList[i]>>16)) {
345                 // same prefix
346
if ((nscode & 0xffff) == (pendingNSList[i] & 0xffff)) {
347                     // same URI
348
return nameCode; // all is well
349
} else {
350                     String JavaDoc prefix = getSubstitutePrefix(nscode, seq);
351
352                     int newCode = namePool.allocate(
353                                         prefix,
354                                         namePool.getURI(nameCode),
355                                         namePool.getLocalName(nameCode));
356                     namespace(namePool.allocateNamespaceCode(newCode), 0);
357                     return newCode;
358                 }
359             }
360         }
361         // no declaration of this prefix: declare it now
362
namespace(nscode, 0);
363         return nameCode;
364     }
365
366     /**
367     * It is possible for a single output element to use the same prefix to refer to different
368     * namespaces. In this case we have to generate an alternative prefix for uniqueness. The
369     * one we generate is based on the sequential position of the element/attribute: this is
370     * designed to ensure both uniqueness (with a high probability) and repeatability
371     */

372
373     private String JavaDoc getSubstitutePrefix(int nscode, int seq) {
374         String JavaDoc prefix = getNamePool().getPrefixFromNamespaceCode(nscode);
375         return prefix + '_' + seq;
376     }
377
378     /**
379     * Output an element end tag.
380     */

381
382     public void endElement() throws XPathException {
383         //System.err.println("Write end tag " + this + " : " + name);
384
if (pendingStartTag >= 0) {
385             startContent();
386         }
387
388         // write the end tag
389

390         receiver.endElement();
391         level--;
392         previousAtomic = false;
393     }
394
395     /**
396     * Write a comment
397     */

398
399     public void comment(CharSequence JavaDoc comment, int locationId, int properties) throws XPathException {
400         if (pendingStartTag >= 0) {
401             startContent();
402         }
403         receiver.comment(comment, locationId, properties);
404         previousAtomic = false;
405     }
406
407     /**
408     * Write a processing instruction
409     */

410
411     public void processingInstruction(String JavaDoc target, CharSequence JavaDoc data, int locationId, int properties) throws XPathException {
412         if (pendingStartTag >= 0) {
413             startContent();
414         }
415         receiver.processingInstruction(target, data, locationId, properties);
416         previousAtomic = false;
417     }
418
419     /**
420     * Append an arbitrary item (node or atomic value) to the output
421      * @param item the item to be appended
422      * @param locationId the location of the calling instruction, for diagnostics
423      * @param copyNamespaces if the item is an element node, this indicates whether its namespaces
424      */

425
426     public void append(Item item, int locationId, int copyNamespaces) throws XPathException {
427         if (item == null) {
428             return;
429         } else if (item instanceof AtomicValue) {
430             if (previousAtomic) {
431                 characters(" ", locationId, 0);
432             }
433             characters(item.getStringValueCS(), locationId, 0);
434             previousAtomic = true;
435         } else if (item instanceof DocumentInfo) {
436             SequenceIterator iter = ((DocumentInfo)item).iterateAxis(Axis.CHILD);
437             while (true) {
438                 Item it = iter.next();
439                 if (it == null) break;
440                 append(it, locationId, copyNamespaces);
441             }
442         } else {
443             ((NodeInfo)item).copy(this, copyNamespaces, true, locationId);
444             previousAtomic = false;
445         }
446     }
447
448
449     /**
450     * Close the output
451     */

452
453     public void close() throws XPathException {
454         // System.err.println("Close " + this + " using emitter " + emitter.getClass());
455
receiver.close();
456         previousAtomic = false;
457     }
458
459     /**
460     * Flush out a pending start tag
461     */

462
463     public void startContent() throws XPathException {
464
465         if (pendingStartTag < 0) {
466             // this can happen if the method is called from outside,
467
// e.g. from a SequenceOutputter earlier in the pipeline
468
return;
469         }
470
471         int props = startElementProperties;
472         int elcode = pendingStartTag;
473         if (declaresDefaultNamespace || (elcode>>20 & 0xff) != 0) {
474             // skip this check if the element is unprefixed and no xmlns="abc" declaration has been encountered
475
elcode = checkProposedPrefix(pendingStartTag, 0);
476             props = startElementProperties | ReceiverOptions.NAMESPACE_OK;
477         }
478         receiver.startElement(elcode, currentSimpleType, startElementLocationId, props);
479
480         for (int a=0; a<pendingAttListSize; a++) {
481             int attcode = pendingAttCode[a];
482             if ((attcode>>20 & 0xff) != 0) { // non-null prefix
483
pendingAttCode[a] = checkProposedPrefix(attcode, a+1);
484             }
485         }
486
487         for (int n=0; n<pendingNSListSize; n++) {
488             receiver.namespace(pendingNSList[n], 0);
489         }
490
491         for (int a=0; a<pendingAttListSize; a++) {
492             receiver.attribute( pendingAttCode[a],
493                                 pendingAttType[a],
494                                 pendingAttValue[a],
495                                 pendingAttLocation[a],
496                                 pendingAttProp[a]);
497         }
498
499         receiver.startContent();
500
501         pendingAttListSize = 0;
502         pendingNSListSize = 0;
503         pendingStartTag = -1;
504         previousAtomic = false;
505     }
506
507 }
508
509 //
510
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
511
// you may not use this file except in compliance with the License. You may obtain a copy of the
512
// License at http://www.mozilla.org/MPL/
513
//
514
// Software distributed under the License is distributed on an "AS IS" basis,
515
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
516
// See the License for the specific language governing rights and limitations under the License.
517
//
518
// The Original Code is: all this file.
519
//
520
// The Initial Developer of the Original Code is Michael H. Kay.
521
//
522
// Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
523
//
524
// Contributor(s): none.
525
//
526
Popular Tags