KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jibx > runtime > impl > XMLWriterBase


1 /*
2 Copyright (c) 2004, Dennis M. Sosnoski.
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7
8  * Redistributions of source code must retain the above copyright notice, this
9    list of conditions and the following disclaimer.
10  * Redistributions in binary form must reproduce the above copyright notice,
11    this list of conditions and the following disclaimer in the documentation
12    and/or other materials provided with the distribution.
13  * Neither the name of JiBX nor the names of its contributors may be used
14    to endorse or promote products derived from this software without specific
15    prior written permission.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
21 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */

28
29 package org.jibx.runtime.impl;
30
31 import java.io.IOException JavaDoc;
32 import java.util.Stack JavaDoc;
33
34 import org.jibx.runtime.IXMLWriter;
35
36 /**
37  * Base implementation of XML writer interface. This provides common handling of
38  * namespaces and indentation that can be used for all forms of text output.
39  *
40  * @author Dennis M. Sosnoski
41  * @version 1.0
42  */

43
44 public abstract class XMLWriterBase implements IXMLWriter
45 {
46     /** URIs for namespaces. */
47     protected String JavaDoc[] m_uris;
48     
49     /** Flag for current element has text content. */
50     protected boolean m_textSeen;
51     
52     /** Flag for current element has content. */
53     protected boolean m_contentSeen;
54     
55     /** Depth of nested tags. */
56     protected int m_nestingDepth;
57     
58     /** Prefixes currently defined for namespaces. */
59     protected String JavaDoc[] m_prefixes;
60     
61     /** Stack of information for namespace declarations. */
62     private Stack JavaDoc m_namespaceStack;
63     
64     /** Depth of top namespace declaration level. */
65     private int m_namespaceDepth;
66     
67     /** Extension namespace URIs (<code>null</code> if not in use). */
68     private String JavaDoc[][] m_extensionUris;
69     
70     /** Extension namespace prefixes (<code>null</code> if not in use). */
71     private String JavaDoc[][] m_extensionPrefixes;
72     
73     /**
74      * Constructor.
75      *
76      * @param uris ordered array of URIs for namespaces used in document (must
77      * be constant; the value in position 0 must always be the empty string "",
78      * and the value in position 1 must always be the XML namespace
79      * "http://www.w3.org/XML/1998/namespace")
80      */

81     
82     public XMLWriterBase(String JavaDoc[] uris) {
83         m_uris = uris;
84         m_prefixes = new String JavaDoc[uris.length];
85         m_prefixes[0] = "";
86         m_prefixes[1] = "xml";
87         m_namespaceStack = new Stack JavaDoc();
88         m_namespaceDepth = -1;
89     }
90     
91     /**
92      * Write markup text to output. Markup text can be written directly to the
93      * output without the need for any escaping, but still needs to be properly
94      * encoded.
95      *
96      * @param text markup text to be written
97      * @throws IOException if error writing to document
98      */

99     
100     protected abstract void writeMarkup(String JavaDoc text) throws IOException JavaDoc;
101     
102     /**
103      * Write markup character to output. Markup text can be written directly to
104      * the output without the need for any escaping, but still needs to be
105      * properly encoded.
106      *
107      * @param chr markup character to be written
108      * @throws IOException if error writing to document
109      */

110     
111     protected abstract void writeMarkup(char chr) throws IOException JavaDoc;
112     
113     /**
114      * Report that namespace has been defined.
115      *
116      * @param index namespace URI index number
117      * @param prefix prefix used for namespace
118      * @throws IOException if error writing to document
119      */

120     
121     protected abstract void defineNamespace(int index, String JavaDoc prefix)
122         throws IOException JavaDoc;
123     
124     /**
125      * Report that namespace has been undefined.
126      *
127      * @param index namespace URI index number
128      */

129     
130     protected abstract void undefineNamespace(int index);
131     
132     /**
133      * Write namespace prefix to output. This internal method is used to throw
134      * an exception when an undeclared prefix is used.
135      *
136      * @param index namespace URI index number
137      * @throws IOException if error writing to document
138      */

139     
140     protected abstract void writePrefix(int index) throws IOException JavaDoc;
141     
142     /**
143      * Write attribute text to output. This needs to write the text with any
144      * appropriate escaping.
145      *
146      * @param text attribute value text to be written
147      * @throws IOException if error writing to document
148      */

149     
150     protected abstract void writeAttributeText(String JavaDoc text) throws IOException JavaDoc;
151         
152     /**
153      * Write XML declaration to document. This can only be called before any
154      * other methods in the interface are called.
155      *
156      * @param version XML version text
157      * @param encoding text for encoding attribute (unspecified if
158      * <code>null</code>)
159      * @param standalone text for standalone attribute (unspecified if
160      * <code>null</code>)
161      * @throws IOException on error writing to document
162      */

163
164     public void writeXMLDecl(String JavaDoc version, String JavaDoc encoding, String JavaDoc standalone)
165         throws IOException JavaDoc {
166         writeMarkup("<?xml version=\"");
167         writeAttributeText(version);
168         if (encoding != null) {
169             writeMarkup("\" encoding=\"");
170             writeAttributeText(encoding);
171         }
172         if (standalone != null) {
173             writeMarkup("\" standalone=\"");
174             writeAttributeText(standalone);
175         }
176         writeMarkup("\"?>");
177     }
178     
179     /**
180      * Generate open start tag. This allows attributes to be added to the start
181      * tag, but must be followed by a {@link #closeStartTag} call.
182      *
183      * @param index namespace URI index number
184      * @param name unqualified element name
185      * @throws IOException on error writing to document
186      */

187
188     public void startTagOpen(int index, String JavaDoc name) throws IOException JavaDoc {
189         indent();
190         writeMarkup('<');
191         writePrefix(index);
192         writeMarkup(name);
193     }
194     
195     /**
196      * Set prefix for namespace.
197      *
198      * @param index namespace URI index number
199      * @return prefix text for namespace, or <code>null</code> if the namespace
200      * is no longer mapped
201      */

202     
203     private void setNamespacePrefix(int index, String JavaDoc prefix) {
204         if (index < m_prefixes.length) {
205             m_prefixes[index] = prefix;
206         } else if (m_extensionUris != null) {
207             index -= m_prefixes.length;
208             for (int i = 0; i < m_extensionUris.length; i++) {
209                 int length = m_extensionUris[i].length;
210                 if (index < length) {
211                     m_extensionPrefixes[i][index] = prefix;
212                     break;
213                 } else {
214                     index -= length;
215                 }
216             }
217         }
218     }
219     
220     /**
221      * Generate start tag for element with namespaces. This creates the actual
222      * start tag, along with any necessary namespace declarations. Previously
223      * active namespace declarations are not duplicated. The tag is
224      * left incomplete, allowing other attributes to be added.
225      *
226      * @param index namespace URI index number
227      * @param name element name
228      * @param nums array of namespace indexes defined by this element (must
229      * be constant, reference is kept until end of element)
230      * @param prefs array of namespace prefixes mapped by this element (no
231      * <code>null</code> values, use "" for default namespace declaration)
232      * @return this context (to allow chained calls)
233      * @throws IOException on error writing to document
234      */

235
236     public void startTagNamespaces(int index, String JavaDoc name,
237         int[] nums, String JavaDoc[] prefs) throws IOException JavaDoc {
238         
239         // find the number of namespaces actually being declared
240
int count = 0;
241         for (int i = 0; i < nums.length; i++) {
242             if (!prefs[i].equals(getNamespacePrefix(nums[i]))) {
243                 count++;
244             }
245         }
246         
247         // check if there's actually any change
248
int[] deltas = null;
249         if (count > 0) {
250             
251             // get the set of namespace indexes that are changing
252
String JavaDoc[] priors = new String JavaDoc[count];
253             if (count == nums.length) {
254                 
255                 // replace the full set, tracking the prior values
256
deltas = nums;
257                 for (int i = 0; i < count; i++) {
258                     int slot = deltas[i];
259                     priors[i] = getNamespacePrefix(slot);
260                     setNamespacePrefix(slot, prefs[i]);
261                     defineNamespace(slot, prefs[i]);
262                 }
263                 
264             } else {
265                 
266                 // replace only changed ones, tracking both indexes and priors
267
int fill = 0;
268                 deltas = new int[count];
269                 for (int i = 0; i < nums.length; i++) {
270                     int slot = nums[i];
271                     String JavaDoc curr = getNamespacePrefix(slot);
272                     if (!prefs[i].equals(curr)) {
273                         deltas[fill] = slot;
274                         priors[fill++] = curr;
275                         setNamespacePrefix(slot, prefs[i]);
276                         defineNamespace(slot, prefs[i]);
277                     }
278                 }
279             }
280             
281             // set up for undeclaring namespaces on close of element
282
m_namespaceStack.push
283                 (new DeclarationInfo(m_nestingDepth, deltas, priors));
284             m_namespaceDepth = m_nestingDepth;
285         }
286         
287         // create the start tag for element
288
startTagOpen(index, name);
289         
290         // add namespace declarations to open element
291
for (int i = 0; i < count; i++) {
292             int slot = deltas[i];
293             String JavaDoc prefix = getNamespacePrefix(slot);
294             if (prefix.length() > 0) {
295                 writeMarkup(" xmlns:");
296                 writeMarkup(prefix);
297                 writeMarkup("=\"");
298             } else {
299                 writeMarkup(" xmlns=\"");
300             }
301             writeAttributeText(getNamespaceUri(slot));
302             writeMarkup('"');
303         }
304     }
305     
306     /**
307      * Add attribute to current open start tag. This is only valid after a call
308      * to {@link #startTagOpen} or {@link #startTagNamespaces} and before the
309      * corresponding call to {@link #closeStartTag}.
310      *
311      * @param index namespace URI index number
312      * @param name unqualified attribute name
313      * @param value text value for attribute
314      * @throws IOException on error writing to document
315      */

316
317     public void addAttribute(int index, String JavaDoc name, String JavaDoc value)
318         throws IOException JavaDoc {
319         writeMarkup(" ");
320         writePrefix(index);
321         writeMarkup(name);
322         writeMarkup("=\"");
323         writeAttributeText(value);
324         writeMarkup('"');
325     }
326     
327     /**
328      * Ends the current innermost set of nested namespace definitions. Reverts
329      * the namespaces involved to their previously-declared prefixes, and sets
330      * up for ending the new innermost set.
331      */

332     
333     public void closeNamespaces() {
334         
335         // revert prefixes for namespaces included in declaration
336
DeclarationInfo info = (DeclarationInfo)m_namespaceStack.pop();
337         int[] deltas = info.m_deltas;
338         String JavaDoc[] priors = info.m_priors;
339         for (int i = 0; i < deltas.length; i++) {
340             int index = deltas[i];
341             undefineNamespace(index);
342             if (index < m_prefixes.length) {
343                 m_prefixes[index] = null;
344             } else if (m_extensionUris != null) {
345                 index -= m_prefixes.length;
346                 for (int j = 0; j < m_extensionUris.length; j++) {
347                     int length = m_extensionUris[j].length;
348                     if (index < length) {
349                         m_extensionPrefixes[j][index] = null;
350                     } else {
351                         index -= length;
352                     }
353                 }
354             }
355         }
356         
357         // set up for clearing next nested set
358
if (m_namespaceStack.empty()) {
359             m_namespaceDepth = -1;
360         } else {
361             m_namespaceDepth =
362                 ((DeclarationInfo)m_namespaceStack.peek()).m_depth;
363         }
364     }
365     
366     /**
367      * Close the current open start tag. This is only valid after a call to
368      * {@link #startTagOpen}.
369      *
370      * @throws IOException on error writing to document
371      */

372
373     public void closeStartTag() throws IOException JavaDoc {
374         writeMarkup('>');
375         m_nestingDepth++;
376         m_textSeen = m_contentSeen = false;
377     }
378     
379     /**
380      * Close the current open start tag as an empty element. This is only valid
381      * after a call to {@link #startTagOpen}.
382      *
383      * @throws IOException on error writing to document
384      */

385
386     public void closeEmptyTag() throws IOException JavaDoc {
387         writeMarkup("/>");
388         if (m_nestingDepth == m_namespaceDepth) {
389             closeNamespaces();
390         }
391         m_contentSeen = true;
392     }
393     
394     /**
395      * Generate closed start tag. No attributes or namespaces can be added to a
396      * start tag written using this call.
397      *
398      * @param index namespace URI index number
399      * @param name unqualified element name
400      * @throws IOException on error writing to document
401      */

402
403     public void startTagClosed(int index, String JavaDoc name) throws IOException JavaDoc {
404         indent();
405         writeMarkup('<');
406         writePrefix(index);
407         writeMarkup(name);
408         writeMarkup('>');
409         m_nestingDepth++;
410         m_textSeen = m_contentSeen = false;
411     }
412     
413     /**
414      * Generate end tag.
415      *
416      * @param index namespace URI index number
417      * @param name unqualified element name
418      * @throws IOException on error writing to document
419      */

420
421     public void endTag(int index, String JavaDoc name) throws IOException JavaDoc {
422         --m_nestingDepth;
423         if (m_contentSeen && !m_textSeen) {
424             indent();
425         }
426         writeMarkup("</");
427         writePrefix(index);
428         writeMarkup(name);
429         writeMarkup('>');
430         if (m_nestingDepth == m_namespaceDepth) {
431             closeNamespaces();
432         }
433         m_textSeen = false;
434         m_contentSeen = true;
435     }
436     
437     /**
438      * Write comment to document.
439      *
440      * @param text comment text
441      * @throws IOException on error writing to document
442      */

443
444     public void writeComment(String JavaDoc text) throws IOException JavaDoc {
445         writeMarkup("<!--");
446         writeMarkup(text);
447         writeMarkup("-->");
448     }
449     
450     /**
451      * Write entity reference to document.
452      *
453      * @param name entity name
454      * @throws IOException on error writing to document
455      */

456
457     public void writeEntityRef(String JavaDoc name) throws IOException JavaDoc {
458         writeMarkup('&');
459         writeMarkup(name);
460         writeMarkup(';');
461         m_contentSeen = true;
462     }
463     
464     /**
465      * Write DOCTYPE declaration to document.
466      *
467      * @param name root element name
468      * @param sys system ID (<code>null</code> if none, must be
469      * non-<code>null</code> for public ID to be used)
470      * @param pub public ID (<code>null</code> if none)
471      * @param subset internal subset (<code>null</code> if none)
472      * @throws IOException on error writing to document
473      */

474
475     public void writeDocType(String JavaDoc name, String JavaDoc sys, String JavaDoc pub, String JavaDoc subset)
476         throws IOException JavaDoc {
477         indent();
478         writeMarkup("<!DOCTYPE ");
479         writeMarkup(name);
480         writeMarkup(' ');
481         if (sys != null) {
482             if (pub == null) {
483                 writeMarkup("SYSTEM \"");
484                 writeMarkup(sys);
485             } else {
486                 writeMarkup("PUBLIC \"");
487                 writeMarkup(pub);
488                 writeMarkup("\" \"");
489                 writeMarkup(sys);
490             }
491             writeMarkup('"');
492         }
493         if (subset != null) {
494             writeMarkup('[');
495             writeMarkup(subset);
496             writeMarkup(']');
497         }
498         writeMarkup('>');
499     }
500     
501     /**
502      * Write processing instruction to document.
503      *
504      * @param target processing instruction target name
505      * @param data processing instruction data
506      * @throws IOException on error writing to document
507      */

508
509     public void writePI(String JavaDoc target, String JavaDoc data) throws IOException JavaDoc {
510         indent();
511         writeMarkup("<?");
512         writeMarkup(target);
513         writeMarkup(' ');
514         writeMarkup(data);
515         writeMarkup("?>");
516         m_contentSeen = true;
517     }
518     
519     /**
520      * Close document output. Completes writing of document output, including
521      * closing the output medium.
522      *
523      * @throws IOException on error writing to document
524      */

525
526     public abstract void close() throws IOException JavaDoc;
527     
528     /**
529      * Reset to initial state for reuse. The context is serially reusable,
530      * as long as this method is called to clear any retained state information
531      * between uses. It is automatically called when output is set.
532      */

533     
534     public void reset() {
535         m_textSeen = m_contentSeen = false;
536         m_nestingDepth = 0;
537         m_namespaceDepth = -1;
538         m_namespaceStack.clear();
539         m_extensionUris = null;
540         m_extensionPrefixes = null;
541     }
542     
543     /**
544      * Get namespace URIs for mapping. This gets the full ordered array of
545      * namespaces known in the binding used for this marshalling, where the
546      * index number of each namespace URI is the namespace index used to lookup
547      * the prefix when marshalling a name in that namespace. The returned array
548      * must not be modified.
549      *
550      * @return array of namespaces
551      */

552     
553     public String JavaDoc[] getNamespaces() {
554         return m_uris;
555     }
556     
557     /**
558      * Get URI for namespace.
559      *
560      * @param index namespace URI index number
561      * @return namespace URI text, or <code>null</code> if the namespace index
562      * is invalid
563      */

564     
565     public String JavaDoc getNamespaceUri(int index) {
566         if (index < m_uris.length) {
567             return m_uris[index];
568         } else if (m_extensionUris != null) {
569             index -= m_uris.length;
570             for (int i = 0; i < m_extensionUris.length; i++) {
571                 int length = m_extensionUris[i].length;
572                 if (index < length) {
573                     return m_extensionUris[i][index];
574                 } else {
575                     index -= length;
576                 }
577             }
578         }
579         return null;
580     }
581     
582     /**
583      * Get current prefix defined for namespace.
584      *
585      * @param index namespace URI index number
586      * @return current prefix text, or <code>null</code> if the namespace is not
587      * currently mapped
588      */

589     
590     public String JavaDoc getNamespacePrefix(int index) {
591         if (index < m_prefixes.length) {
592             return m_prefixes[index];
593         } else if (m_extensionUris != null) {
594             index -= m_prefixes.length;
595             for (int i = 0; i < m_extensionUris.length; i++) {
596                 int length = m_extensionUris[i].length;
597                 if (index < length) {
598                     return m_extensionPrefixes[i][index];
599                 } else {
600                     index -= length;
601                 }
602             }
603         }
604         return null;
605     }
606     
607     /**
608      * Get index of namespace mapped to prefix. This can be an expensive
609      * operation with time proportional to the number of namespaces defined, so
610      * it should be used with care.
611      *
612      * @param prefix text to match
613      * @return index namespace URI index number mapped to prefix
614      */

615     
616     public int getPrefixIndex(String JavaDoc prefix) {
617         if (m_extensionPrefixes != null) {
618             for (int i = m_extensionPrefixes.length-1; i >= 0; i--) {
619                 String JavaDoc[] prefixes = m_extensionPrefixes[i];
620                 for (int j = prefixes.length-1; j >= 0; j--) {
621                     if (prefix.equals(prefixes[j])) {
622                         int index = j + m_prefixes.length;
623                         for (int k = i-1; k >= 0; k--) {
624                             index += m_extensionPrefixes[k].length;
625                         }
626                         return index;
627                     }
628                 }
629             }
630         }
631         for (int i = m_prefixes.length-1; i >= 0; i--) {
632             if (prefix.equals(m_prefixes[i])) {
633                 return i;
634             }
635         }
636         return -1;
637     }
638     
639     /**
640      * Grow array of array of strings.
641      *
642      * @param base array to be grown (<code>null</code> is treated as zero
643      * length)
644      * @param items array of strings to be added at end of base array
645      * @return array with added array of items
646      */

647     
648     protected static String JavaDoc[][] growArray(String JavaDoc[][] base, String JavaDoc[] items) {
649         if (base == null) {
650             return new String JavaDoc[][] { items };
651         } else {
652             int length = base.length;
653             String JavaDoc[][] grow = new String JavaDoc[length+1][];
654             System.arraycopy(base, 0, grow, 0, length);
655             grow[length] = items;
656             return grow;
657         }
658     }
659     
660     /**
661      * Shrink array of array of strings.
662      *
663      * @param base array to be shrunk
664      * @return array with last set of items eliminated (<code>null</code> if
665      * empty)
666      */

667     
668     protected static String JavaDoc[][] shrinkArray(String JavaDoc[][] base) {
669         int length = base.length;
670         if (length == 1) {
671             return null;
672         } else {
673             String JavaDoc[][] shrink = new String JavaDoc[length-1][];
674             System.arraycopy(base, 0, shrink, 0, length-1);
675             return shrink;
676         }
677     }
678     
679     /**
680      * Append extension namespace URIs to those in mapping.
681      *
682      * @param uris namespace URIs to extend those in mapping
683      */

684     
685     public void pushExtensionNamespaces(String JavaDoc[] uris) {
686         m_extensionUris = growArray(m_extensionUris, uris);
687         m_extensionPrefixes =
688             growArray(m_extensionPrefixes, new String JavaDoc[uris.length]);
689     }
690     
691     /**
692      * Remove extension namespace URIs. This removes the last set of
693      * extension namespaces pushed using {@link #pushExtensionNamespaces}.
694      */

695     
696     public void popExtensionNamespaces() {
697         m_extensionUris = shrinkArray(m_extensionUris);
698         m_extensionPrefixes = shrinkArray(m_extensionPrefixes);
699     }
700     
701     /**
702      * Get extension namespace URIs added to those in mapping. This gets the
703      * current set of extension definitions. The returned arrays must not be
704      * modified.
705      *
706      * @return array of arrays of extension namespaces (<code>null</code> if
707      * none)
708      */

709     
710     public String JavaDoc[][] getExtensionNamespaces() {
711         return m_extensionUris;
712     }
713     
714     /**
715      * Namespace declaration tracking information. This tracks all information
716      * associated with an element that declares namespaces.
717      */

718     
719     private static class DeclarationInfo
720     {
721         /** Depth of element making declaration. */
722         public final int m_depth;
723         
724         /** Indexes of namespaces included in declarations. */
725         public final int[] m_deltas;
726         
727         /** Prior prefixes for namespaces. */
728         public final String JavaDoc[] m_priors;
729         
730         /** Simple constructor. */
731         public DeclarationInfo(int depth, int[] deltas, String JavaDoc[] priors) {
732             m_depth = depth;
733             m_deltas = deltas;
734             m_priors = priors;
735         }
736     }
737 }
Popular Tags