~vcs-imports/xena/trunk

« back to all changes in this revision

Viewing changes to ext/src/xerces-2_9_1/src/org/apache/xml/serialize/BaseMarkupSerializer.java

  • Committer: matthewoliver
  • Date: 2009-12-10 03:18:07 UTC
  • Revision ID: vcs-imports@canonical.com-20091210031807-l086qguzdlljtkl9
Merged Xena Testing into Xena Stable for the Xena 5 release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
 
3
 * contributor license agreements.  See the NOTICE file distributed with
 
4
 * this work for additional information regarding copyright ownership.
 
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
 
6
 * (the "License"); you may not use this file except in compliance with
 
7
 * the License.  You may obtain a copy of the License at
 
8
 * 
 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
 
10
 * 
 
11
 * Unless required by applicable law or agreed to in writing, software
 
12
 * distributed under the License is distributed on an "AS IS" BASIS,
 
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
14
 * See the License for the specific language governing permissions and
 
15
 * limitations under the License.
 
16
 */
 
17
 
 
18
// Sep 14, 2000:
 
19
//  Fixed comments to preserve whitespaces and add a line break
 
20
//  when indenting. Reported by Gervase Markham <gerv@gerv.net>
 
21
// Sep 14, 2000:
 
22
//  Fixed serializer to report IO exception directly, instead at
 
23
//  the end of document processing.
 
24
//  Reported by Patrick Higgins <phiggins@transzap.com>
 
25
// Sep 13, 2000:
 
26
//   CR in character data will print as &#0D;
 
27
// Aug 25, 2000:
 
28
//   Fixed processing instruction printing inside element content
 
29
//   to not escape content. Reported by Mikael Staldal
 
30
//   <d96-mst@d.kth.se>
 
31
// Aug 25, 2000:
 
32
//   Added ability to omit comments.
 
33
//   Contributed by Anupam Bagchi <abagchi@jtcsv.com>
 
34
// Aug 26, 2000:
 
35
//   Fixed bug in newline handling when preserving spaces.
 
36
//   Contributed by Mike Dusseault <mdusseault@home.com>
 
37
// Aug 29, 2000:
 
38
//   Fixed state.unescaped not being set to false when
 
39
//   entering element state.
 
40
//   Reported by Lowell Vaughn <lvaughn@agillion.com>
 
41
 
 
42
package org.apache.xml.serialize;
 
43
 
 
44
import java.io.IOException;
 
45
import java.io.OutputStream;
 
46
import java.io.Writer;
 
47
import java.util.Hashtable;
 
48
import java.util.Vector;
 
49
 
 
50
import org.apache.xerces.dom.DOMErrorImpl;
 
51
import org.apache.xerces.dom.DOMLocatorImpl;
 
52
import org.apache.xerces.dom.DOMMessageFormatter;
 
53
import org.apache.xerces.util.XMLChar;
 
54
import org.w3c.dom.DOMError;
 
55
import org.w3c.dom.DOMErrorHandler;
 
56
import org.w3c.dom.Document;
 
57
import org.w3c.dom.DocumentFragment;
 
58
import org.w3c.dom.DocumentType;
 
59
import org.w3c.dom.Element;
 
60
import org.w3c.dom.Node;
 
61
import org.w3c.dom.ls.LSException;
 
62
import org.w3c.dom.ls.LSSerializer;
 
63
import org.w3c.dom.ls.LSSerializerFilter;
 
64
import org.w3c.dom.traversal.NodeFilter;
 
65
import org.xml.sax.ContentHandler;
 
66
import org.xml.sax.DTDHandler;
 
67
import org.xml.sax.DocumentHandler;
 
68
import org.xml.sax.Locator;
 
69
import org.xml.sax.SAXException;
 
70
import org.xml.sax.ext.DeclHandler;
 
71
import org.xml.sax.ext.LexicalHandler;
 
72
 
 
73
/**
 
74
 * Base class for a serializer supporting both DOM and SAX pretty
 
75
 * serializing of XML/HTML/XHTML documents. Derives classes perform
 
76
 * the method-specific serializing, this class provides the common
 
77
 * serializing mechanisms.
 
78
 * <p>
 
79
 * The serializer must be initialized with the proper writer and
 
80
 * output format before it can be used by calling {@link #setOutputCharStream} 
 
81
 * or {@link #setOutputByteStream} for the writer and {@link #setOutputFormat}
 
82
 * for the output format.
 
83
 * <p>
 
84
 * The serializer can be reused any number of times, but cannot
 
85
 * be used concurrently by two threads.
 
86
 * <p>
 
87
 * If an output stream is used, the encoding is taken from the
 
88
 * output format (defaults to <tt>UTF-8</tt>). If a writer is
 
89
 * used, make sure the writer uses the same encoding (if applies)
 
90
 * as specified in the output format.
 
91
 * <p>
 
92
 * The serializer supports both DOM and SAX. DOM serializing is done
 
93
 * by calling {@link #serialize(Document)} and SAX serializing is done by firing
 
94
 * SAX events and using the serializer as a document handler.
 
95
 * This also applies to derived class.
 
96
 * <p>
 
97
 * If an I/O exception occurs while serializing, the serializer
 
98
 * will not throw an exception directly, but only throw it
 
99
 * at the end of serializing (either DOM or SAX's {@link
 
100
 * org.xml.sax.DocumentHandler#endDocument}.
 
101
 * <p>
 
102
 * For elements that are not specified as whitespace preserving,
 
103
 * the serializer will potentially break long text lines at space
 
104
 * boundaries, indent lines, and serialize elements on separate
 
105
 * lines. Line terminators will be regarded as spaces, and
 
106
 * spaces at beginning of line will be stripped.
 
107
 * <p>
 
108
 * When indenting, the serializer is capable of detecting seemingly
 
109
 * element content, and serializing these elements indented on separate
 
110
 * lines. An element is serialized indented when it is the first or
 
111
 * last child of an element, or immediate following or preceding
 
112
 * another element.
 
113
 *
 
114
 * @deprecated This class was deprecated in Xerces 2.9.0. It is recommended 
 
115
 * that new applications use the DOM Level 3 LSSerializer or JAXP's Transformation 
 
116
 * API for XML (TrAX) for serializing XML. See the Xerces documentation for more 
 
117
 * information.
 
118
 * @version $Revision: 1.2 $ $Date: 2009/12/10 03:18:31 $
 
119
 * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
 
120
 * @author <a href="mailto:rahul.srivastava@sun.com">Rahul Srivastava</a>
 
121
 * @author Elena Litani, IBM 
 
122
 * @see Serializer
 
123
 * @see org.w3c.dom.ls.LSSerializer
 
124
 */
 
125
public abstract class BaseMarkupSerializer
 
126
    implements ContentHandler, DocumentHandler, LexicalHandler,
 
127
               DTDHandler, DeclHandler, DOMSerializer, Serializer
 
128
{
 
129
 
 
130
    // DOM L3 implementation
 
131
    protected short features = 0xFFFFFFFF;
 
132
    protected DOMErrorHandler fDOMErrorHandler;
 
133
    protected final DOMErrorImpl fDOMError = new DOMErrorImpl();
 
134
    protected LSSerializerFilter fDOMFilter;
 
135
 
 
136
    protected EncodingInfo _encodingInfo;
 
137
 
 
138
 
 
139
    /**
 
140
     * Holds array of all element states that have been entered.
 
141
     * The array is automatically resized. When leaving an element,
 
142
     * it's state is not removed but reused when later returning
 
143
     * to the same nesting level.
 
144
     */
 
145
    private ElementState[]  _elementStates;
 
146
 
 
147
 
 
148
    /**
 
149
     * The index of the next state to place in the array,
 
150
     * or one plus the index of the current state. When zero,
 
151
     * we are in no state.
 
152
     */
 
153
    private int             _elementStateCount;
 
154
 
 
155
 
 
156
    /**
 
157
     * Vector holding comments and PIs that come before the root
 
158
     * element (even after it), see {@link #serializePreRoot}.
 
159
     */
 
160
    private Vector          _preRoot;
 
161
 
 
162
 
 
163
    /**
 
164
     * If the document has been started (header serialized), this
 
165
     * flag is set to true so it's not started twice.
 
166
     */
 
167
    protected boolean       _started;
 
168
 
 
169
 
 
170
    /**
 
171
     * True if the serializer has been prepared. This flag is set
 
172
     * to false when the serializer is reset prior to using it,
 
173
     * and to true after it has been prepared for usage.
 
174
     */
 
175
    private boolean         _prepared;
 
176
 
 
177
 
 
178
    /**
 
179
     * Association between namespace URIs (keys) and prefixes (values).
 
180
     * Accumulated here prior to starting an element and placing this
 
181
     * list in the element state.
 
182
     */
 
183
    protected Hashtable     _prefixes;
 
184
 
 
185
 
 
186
    /**
 
187
     * The system identifier of the document type, if known.
 
188
     */
 
189
    protected String        _docTypePublicId;
 
190
 
 
191
 
 
192
    /**
 
193
     * The system identifier of the document type, if known.
 
194
     */
 
195
    protected String        _docTypeSystemId;
 
196
 
 
197
 
 
198
    /**
 
199
     * The output format associated with this serializer. This will never
 
200
     * be a null reference. If no format was passed to the constructor,
 
201
     * the default one for this document type will be used. The format
 
202
     * object is never changed by the serializer.
 
203
     */
 
204
    protected OutputFormat   _format;
 
205
 
 
206
 
 
207
    /**
 
208
     * The printer used for printing text parts.
 
209
     */
 
210
    protected Printer       _printer;
 
211
 
 
212
 
 
213
    /**
 
214
     * True if indenting printer.
 
215
     */
 
216
    protected boolean       _indenting;
 
217
 
 
218
    /** Temporary buffer to store character data */
 
219
    protected final StringBuffer fStrBuffer = new StringBuffer(40);
 
220
 
 
221
    /**
 
222
     * The underlying writer.
 
223
     */
 
224
    private Writer          _writer;
 
225
 
 
226
 
 
227
    /**
 
228
     * The output stream.
 
229
     */
 
230
    private OutputStream    _output;
 
231
 
 
232
    /** Current node that is being processed  */
 
233
    protected Node fCurrentNode = null;
 
234
 
 
235
    
 
236
 
 
237
    //--------------------------------//
 
238
    // Constructor and initialization //
 
239
    //--------------------------------//
 
240
 
 
241
 
 
242
    /**
 
243
     * Protected constructor can only be used by derived class.
 
244
     * Must initialize the serializer before serializing any document,
 
245
     * by calling {@link #setOutputCharStream} or {@link #setOutputByteStream}
 
246
                 * first
 
247
     */
 
248
    protected BaseMarkupSerializer( OutputFormat format )
 
249
    {
 
250
        int i;
 
251
 
 
252
        _elementStates = new ElementState[ 10 ];
 
253
        for ( i = 0 ; i < _elementStates.length ; ++i )
 
254
            _elementStates[ i ] = new ElementState();
 
255
        _format = format;
 
256
    }
 
257
 
 
258
 
 
259
    public DocumentHandler asDocumentHandler()
 
260
        throws IOException
 
261
    {
 
262
        prepare();
 
263
        return this;
 
264
    }
 
265
 
 
266
 
 
267
    public ContentHandler asContentHandler()
 
268
        throws IOException
 
269
    {
 
270
        prepare();
 
271
        return this;
 
272
    }
 
273
 
 
274
 
 
275
    public DOMSerializer asDOMSerializer()
 
276
        throws IOException
 
277
    {
 
278
        prepare();
 
279
        return this;
 
280
    }
 
281
 
 
282
 
 
283
    public void setOutputByteStream( OutputStream output )
 
284
    {
 
285
        if ( output == null ) {
 
286
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN,
 
287
                                                           "ArgumentIsNull", new Object[]{"output"});
 
288
            throw new NullPointerException(msg);
 
289
        }
 
290
        _output = output;
 
291
        _writer = null;
 
292
        reset();
 
293
    }
 
294
 
 
295
 
 
296
    public void setOutputCharStream( Writer writer )
 
297
    {
 
298
        if ( writer == null ) {
 
299
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN,
 
300
                                                           "ArgumentIsNull", new Object[]{"writer"});
 
301
            throw new NullPointerException(msg);
 
302
        }
 
303
        _writer = writer;
 
304
        _output = null;
 
305
        reset();
 
306
    }
 
307
 
 
308
 
 
309
    public void setOutputFormat( OutputFormat format )
 
310
    {
 
311
        if ( format == null ) {
 
312
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN,
 
313
                                                           "ArgumentIsNull", new Object[]{"format"});
 
314
            throw new NullPointerException(msg);
 
315
        }
 
316
        _format = format;
 
317
        reset();
 
318
    }
 
319
 
 
320
 
 
321
    public boolean reset()
 
322
    {
 
323
        if ( _elementStateCount > 1 ) {
 
324
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN,
 
325
                                                           "ResetInMiddle", null);
 
326
            throw new IllegalStateException(msg);
 
327
        }
 
328
        _prepared = false;
 
329
        fCurrentNode = null;
 
330
        fStrBuffer.setLength(0);
 
331
        return true;
 
332
    }
 
333
 
 
334
 
 
335
    protected void prepare()
 
336
        throws IOException
 
337
    {
 
338
        if ( _prepared )
 
339
            return;
 
340
 
 
341
        if ( _writer == null && _output == null ) {
 
342
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN,
 
343
                                                           "NoWriterSupplied", null);
 
344
            throw new IOException(msg);
 
345
        }
 
346
        // If the output stream has been set, use it to construct
 
347
        // the writer. It is possible that the serializer has been
 
348
        // reused with the same output stream and different encoding.
 
349
 
 
350
        _encodingInfo = _format.getEncodingInfo();
 
351
 
 
352
        if ( _output != null ) {
 
353
            _writer = _encodingInfo.getWriter(_output);
 
354
        }
 
355
 
 
356
        if ( _format.getIndenting() ) {
 
357
            _indenting = true;
 
358
            _printer = new IndentPrinter( _writer, _format );
 
359
        } else {
 
360
            _indenting = false;
 
361
            _printer = new Printer( _writer, _format );
 
362
        }
 
363
 
 
364
        ElementState state;
 
365
 
 
366
        _elementStateCount = 0;
 
367
        state = _elementStates[ 0 ];
 
368
        state.namespaceURI = null;
 
369
        state.localName = null;
 
370
        state.rawName = null;
 
371
        state.preserveSpace = _format.getPreserveSpace();
 
372
        state.empty = true;
 
373
        state.afterElement = false;
 
374
        state.afterComment = false;
 
375
        state.doCData = state.inCData = false;
 
376
        state.prefixes = null;
 
377
 
 
378
        _docTypePublicId = _format.getDoctypePublic();
 
379
        _docTypeSystemId = _format.getDoctypeSystem();
 
380
        _started = false;
 
381
        _prepared = true;
 
382
    }
 
383
 
 
384
 
 
385
 
 
386
    //----------------------------------//
 
387
    // DOM document serializing methods //
 
388
    //----------------------------------//
 
389
 
 
390
 
 
391
    /**
 
392
     * Serializes the DOM element using the previously specified
 
393
     * writer and output format. Throws an exception only if
 
394
     * an I/O exception occured while serializing.
 
395
     *
 
396
     * @param elem The element to serialize
 
397
     * @throws IOException An I/O exception occured while
 
398
     *   serializing
 
399
     */
 
400
    public void serialize( Element elem )
 
401
        throws IOException
 
402
    {
 
403
        reset();
 
404
        prepare();
 
405
        serializeNode( elem );
 
406
        _printer.flush();
 
407
        if ( _printer.getException() != null )
 
408
            throw _printer.getException();
 
409
    }
 
410
 
 
411
 
 
412
    /**
 
413
     * Serializes the DOM document fragmnt using the previously specified
 
414
     * writer and output format. Throws an exception only if
 
415
     * an I/O exception occured while serializing.
 
416
     *
 
417
     * @param frag The document fragment to serialize
 
418
     * @throws IOException An I/O exception occured while
 
419
     *   serializing
 
420
     */
 
421
    public void serialize( DocumentFragment frag )
 
422
        throws IOException
 
423
    {
 
424
        reset();
 
425
        prepare();
 
426
        serializeNode( frag );
 
427
        _printer.flush();
 
428
        if ( _printer.getException() != null )
 
429
            throw _printer.getException();
 
430
    }
 
431
 
 
432
 
 
433
    /**
 
434
     * Serializes the DOM document using the previously specified
 
435
     * writer and output format. Throws an exception only if
 
436
     * an I/O exception occured while serializing.
 
437
     *
 
438
     * @param doc The document to serialize
 
439
     * @throws IOException An I/O exception occured while
 
440
     *   serializing
 
441
     */
 
442
    public void serialize( Document doc )
 
443
        throws IOException
 
444
    {
 
445
        reset();
 
446
        prepare();
 
447
        serializeNode( doc );
 
448
        serializePreRoot();
 
449
        _printer.flush();
 
450
        if ( _printer.getException() != null )
 
451
            throw _printer.getException();
 
452
    }
 
453
 
 
454
 
 
455
    //------------------------------------------//
 
456
    // SAX document handler serializing methods //
 
457
    //------------------------------------------//
 
458
 
 
459
 
 
460
    public void startDocument()
 
461
        throws SAXException
 
462
    {
 
463
        try {
 
464
            prepare();
 
465
        } catch ( IOException except ) {
 
466
            throw new SAXException( except.toString() );
 
467
        }
 
468
        // Nothing to do here. All the magic happens in startDocument(String)
 
469
    }
 
470
    
 
471
    
 
472
    public void characters( char[] chars, int start, int length )
 
473
        throws SAXException
 
474
    {
 
475
        ElementState state;
 
476
 
 
477
        try {
 
478
        state = content();
 
479
 
 
480
        // Check if text should be print as CDATA section or unescaped
 
481
        // based on elements listed in the output format (the element
 
482
        // state) or whether we are inside a CDATA section or entity.
 
483
 
 
484
        if ( state.inCData || state.doCData ) {
 
485
            int          saveIndent;
 
486
 
 
487
            // Print a CDATA section. The text is not escaped, but ']]>'
 
488
            // appearing in the code must be identified and dealt with.
 
489
            // The contents of a text node is considered space preserving.
 
490
            if ( ! state.inCData ) {
 
491
                _printer.printText( "<![CDATA[" );
 
492
                state.inCData = true;
 
493
            }
 
494
            saveIndent = _printer.getNextIndent();
 
495
            _printer.setNextIndent( 0 );
 
496
            char ch;
 
497
            final int end = start + length;
 
498
            for ( int index = start ; index < end; ++index ) {
 
499
                ch = chars[index];
 
500
                if ( ch == ']' && index + 2 < end &&
 
501
                     chars[ index + 1 ] == ']' && chars[ index + 2 ] == '>' ) {
 
502
                    _printer.printText("]]]]><![CDATA[>");
 
503
                    index +=2; 
 
504
                    continue;
 
505
                }
 
506
                if (!XMLChar.isValid(ch)) {
 
507
                    // check if it is surrogate
 
508
                    if (++index < end) {
 
509
                        surrogates(ch, chars[index], true);
 
510
                    } 
 
511
                    else {
 
512
                        fatalError("The character '"+ch+"' is an invalid XML character"); 
 
513
                    }
 
514
                    continue;
 
515
                }
 
516
                if ( ( ch >= ' ' && _encodingInfo.isPrintable(ch) && ch != 0xF7 ) ||
 
517
                    ch == '\n' || ch == '\r' || ch == '\t' ) {
 
518
                    _printer.printText(ch);
 
519
                } 
 
520
                else {
 
521
                    // The character is not printable -- split CDATA section
 
522
                    _printer.printText("]]>&#x");                        
 
523
                    _printer.printText(Integer.toHexString(ch));                        
 
524
                    _printer.printText(";<![CDATA[");
 
525
                }
 
526
            }
 
527
            _printer.setNextIndent( saveIndent );
 
528
 
 
529
        } else {
 
530
 
 
531
            int saveIndent;
 
532
 
 
533
            if ( state.preserveSpace ) {
 
534
                // If preserving space then hold of indentation so no
 
535
                // excessive spaces are printed at line breaks, escape
 
536
                // the text content without replacing spaces and print
 
537
                // the text breaking only at line breaks.
 
538
                saveIndent = _printer.getNextIndent();
 
539
                _printer.setNextIndent( 0 );
 
540
                printText( chars, start, length, true, state.unescaped );
 
541
                _printer.setNextIndent( saveIndent );
 
542
            } else {
 
543
                printText( chars, start, length, false, state.unescaped );
 
544
            }
 
545
        }
 
546
        } catch ( IOException except ) {
 
547
            throw new SAXException( except );
 
548
        }
 
549
    }
 
550
 
 
551
 
 
552
    public void ignorableWhitespace( char[] chars, int start, int length )
 
553
        throws SAXException
 
554
    {
 
555
        int i;
 
556
 
 
557
        try {
 
558
        content();
 
559
 
 
560
        // Print ignorable whitespaces only when indenting, after
 
561
        // all they are indentation. Cancel the indentation to
 
562
        // not indent twice.
 
563
        if ( _indenting ) {
 
564
            _printer.setThisIndent( 0 );
 
565
            for ( i = start ; length-- > 0 ; ++i )
 
566
                _printer.printText( chars[ i ] );
 
567
        }
 
568
        } catch ( IOException except ) {
 
569
            throw new SAXException( except );
 
570
        }
 
571
    }
 
572
 
 
573
 
 
574
    public final void processingInstruction( String target, String code )
 
575
        throws SAXException
 
576
    {
 
577
        try {
 
578
            processingInstructionIO( target, code );
 
579
        } catch ( IOException except ) {
 
580
        throw new SAXException( except );
 
581
        }
 
582
    }
 
583
 
 
584
    public void processingInstructionIO( String target, String code )
 
585
        throws IOException
 
586
    {
 
587
        int          index;
 
588
        ElementState state;
 
589
 
 
590
        state = content();
 
591
 
 
592
        // Create the processing instruction textual representation.
 
593
        // Make sure we don't have '?>' inside either target or code.
 
594
        index = target.indexOf( "?>" );
 
595
        if ( index >= 0 )
 
596
            fStrBuffer.append( "<?" ).append( target.substring( 0, index ) );
 
597
        else
 
598
            fStrBuffer.append( "<?" ).append( target );
 
599
        if ( code != null ) {
 
600
            fStrBuffer.append( ' ' );
 
601
            index = code.indexOf( "?>" );
 
602
            if ( index >= 0 )
 
603
                fStrBuffer.append( code.substring( 0, index ) );
 
604
            else
 
605
                fStrBuffer.append( code );
 
606
        }
 
607
        fStrBuffer.append( "?>" );
 
608
 
 
609
        // If before the root element (or after it), do not print
 
610
        // the PI directly but place it in the pre-root vector.
 
611
        if ( isDocumentState() ) {
 
612
            if ( _preRoot == null )
 
613
                _preRoot = new Vector();
 
614
            _preRoot.addElement( fStrBuffer.toString() );
 
615
        } else {
 
616
            _printer.indent();
 
617
            printText( fStrBuffer.toString(), true, true );
 
618
            _printer.unindent();
 
619
            if ( _indenting )
 
620
            state.afterElement = true;
 
621
        }
 
622
 
 
623
        fStrBuffer.setLength(0);
 
624
    }
 
625
 
 
626
 
 
627
    public void comment( char[] chars, int start, int length )
 
628
        throws SAXException
 
629
    {
 
630
        try {
 
631
        comment( new String( chars, start, length ) );
 
632
        } catch ( IOException except ) {
 
633
            throw new SAXException( except );
 
634
    }
 
635
    }
 
636
 
 
637
 
 
638
    public void comment( String text )
 
639
        throws IOException
 
640
    {
 
641
        int          index;
 
642
        ElementState state;
 
643
        
 
644
        if ( _format.getOmitComments() )
 
645
            return;
 
646
 
 
647
        state  = content();
 
648
        // Create the processing comment textual representation.
 
649
        // Make sure we don't have '-->' inside the comment.
 
650
        index = text.indexOf( "-->" );
 
651
        if ( index >= 0 )
 
652
            fStrBuffer.append( "<!--" ).append( text.substring( 0, index ) ).append( "-->" );
 
653
        else
 
654
            fStrBuffer.append( "<!--" ).append( text ).append( "-->" );
 
655
 
 
656
        // If before the root element (or after it), do not print
 
657
        // the comment directly but place it in the pre-root vector.
 
658
        if ( isDocumentState() ) {
 
659
            if ( _preRoot == null )
 
660
                _preRoot = new Vector();
 
661
            _preRoot.addElement( fStrBuffer.toString() );
 
662
        } else {
 
663
            // Indent this element on a new line if the first
 
664
            // content of the parent element or immediately
 
665
            // following an element.
 
666
            if ( _indenting && ! state.preserveSpace)
 
667
                _printer.breakLine();
 
668
                                                _printer.indent();
 
669
            printText( fStrBuffer.toString(), true, true );
 
670
                                                _printer.unindent();
 
671
            if ( _indenting )
 
672
                state.afterElement = true;
 
673
        }
 
674
 
 
675
        fStrBuffer.setLength(0);
 
676
        state.afterComment = true;
 
677
        state.afterElement = false;
 
678
    }
 
679
 
 
680
 
 
681
    public void startCDATA()
 
682
    {
 
683
        ElementState state;
 
684
 
 
685
        state = getElementState();
 
686
        state.doCData = true;
 
687
    }
 
688
 
 
689
 
 
690
    public void endCDATA()
 
691
    {
 
692
        ElementState state;
 
693
 
 
694
        state = getElementState();
 
695
        state.doCData = false;
 
696
    }
 
697
 
 
698
 
 
699
    public void startNonEscaping()
 
700
    {
 
701
        ElementState state;
 
702
 
 
703
        state = getElementState();
 
704
        state.unescaped = true;
 
705
    }
 
706
 
 
707
 
 
708
    public void endNonEscaping()
 
709
    {
 
710
        ElementState state;
 
711
 
 
712
        state = getElementState();
 
713
        state.unescaped = false;
 
714
    }
 
715
 
 
716
 
 
717
    public void startPreserving()
 
718
    {
 
719
        ElementState state;
 
720
 
 
721
        state = getElementState();
 
722
        state.preserveSpace = true;
 
723
    }
 
724
 
 
725
 
 
726
    public void endPreserving()
 
727
    {
 
728
        ElementState state;
 
729
 
 
730
        state = getElementState();
 
731
        state.preserveSpace = false;
 
732
    }
 
733
 
 
734
 
 
735
    /**
 
736
     * Called at the end of the document to wrap it up.
 
737
     * Will flush the output stream and throw an exception
 
738
     * if any I/O error occured while serializing.
 
739
     *
 
740
     * @throws SAXException An I/O exception occured during
 
741
     *  serializing
 
742
     */
 
743
    public void endDocument()
 
744
        throws SAXException
 
745
    {
 
746
        try {
 
747
        // Print all the elements accumulated outside of
 
748
        // the root element.
 
749
        serializePreRoot();
 
750
        // Flush the output, this is necessary for fStrBuffered output.
 
751
        _printer.flush();
 
752
        } catch ( IOException except ) {
 
753
            throw new SAXException( except );
 
754
    }
 
755
    }
 
756
 
 
757
 
 
758
    public void startEntity( String name )
 
759
    {
 
760
        // ???
 
761
    }
 
762
 
 
763
 
 
764
    public void endEntity( String name )
 
765
    {
 
766
        // ???
 
767
    }
 
768
 
 
769
 
 
770
    public void setDocumentLocator( Locator locator )
 
771
    {
 
772
        // Nothing to do
 
773
    }
 
774
 
 
775
 
 
776
    //-----------------------------------------//
 
777
    // SAX content handler serializing methods //
 
778
    //-----------------------------------------//
 
779
 
 
780
 
 
781
    public void skippedEntity ( String name )
 
782
        throws SAXException
 
783
    {
 
784
        try {
 
785
        endCDATA();
 
786
        content();
 
787
        _printer.printText( '&' );
 
788
        _printer.printText( name );
 
789
        _printer.printText( ';' );
 
790
        } catch ( IOException except ) {
 
791
            throw new SAXException( except );
 
792
    }
 
793
    }
 
794
 
 
795
 
 
796
    public void startPrefixMapping( String prefix, String uri )
 
797
        throws SAXException
 
798
    {
 
799
        if ( _prefixes == null )
 
800
            _prefixes = new Hashtable();
 
801
        _prefixes.put( uri, prefix == null ? "" : prefix );
 
802
    }
 
803
 
 
804
 
 
805
    public void endPrefixMapping( String prefix )
 
806
        throws SAXException
 
807
    {
 
808
    }
 
809
 
 
810
 
 
811
    //------------------------------------------//
 
812
    // SAX DTD/Decl handler serializing methods //
 
813
    //------------------------------------------//
 
814
 
 
815
 
 
816
    public final void startDTD( String name, String publicId, String systemId )
 
817
        throws SAXException
 
818
    {
 
819
        try {
 
820
        _printer.enterDTD();
 
821
        _docTypePublicId = publicId;
 
822
        _docTypeSystemId = systemId;
 
823
        } catch ( IOException except ) {
 
824
            throw new SAXException( except );
 
825
        }
 
826
    }
 
827
 
 
828
 
 
829
    public void endDTD()
 
830
    {
 
831
        // Nothing to do here, all the magic occurs in startDocument(String).
 
832
    }
 
833
 
 
834
 
 
835
    public void elementDecl( String name, String model )
 
836
        throws SAXException
 
837
    {
 
838
        try {
 
839
        _printer.enterDTD();
 
840
        _printer.printText( "<!ELEMENT " );
 
841
        _printer.printText( name );
 
842
        _printer.printText( ' ' );
 
843
        _printer.printText( model );
 
844
        _printer.printText( '>' );
 
845
        if ( _indenting )
 
846
            _printer.breakLine();
 
847
        } catch ( IOException except ) {
 
848
            throw new SAXException( except );
 
849
        }
 
850
    }
 
851
 
 
852
 
 
853
    public void attributeDecl( String eName, String aName, String type,
 
854
                               String valueDefault, String value )
 
855
        throws SAXException
 
856
    {
 
857
        try {
 
858
        _printer.enterDTD();
 
859
        _printer.printText( "<!ATTLIST " );
 
860
        _printer.printText( eName );
 
861
        _printer.printText( ' ' );
 
862
        _printer.printText( aName );
 
863
        _printer.printText( ' ' );
 
864
        _printer.printText( type );
 
865
        if ( valueDefault != null ) {
 
866
            _printer.printText( ' ' );
 
867
            _printer.printText( valueDefault );
 
868
        }
 
869
        if ( value != null ) {
 
870
            _printer.printText( " \"" );
 
871
            printEscaped( value );
 
872
            _printer.printText( '"' );
 
873
        }
 
874
        _printer.printText( '>' );
 
875
        if ( _indenting )
 
876
            _printer.breakLine();
 
877
        } catch ( IOException except ) {
 
878
            throw new SAXException( except );
 
879
    }
 
880
    }
 
881
 
 
882
 
 
883
    public void internalEntityDecl( String name, String value )
 
884
        throws SAXException
 
885
    {
 
886
        try {
 
887
        _printer.enterDTD();
 
888
        _printer.printText( "<!ENTITY " );
 
889
        _printer.printText( name );
 
890
        _printer.printText( " \"" );
 
891
        printEscaped( value );
 
892
        _printer.printText( "\">" );
 
893
        if ( _indenting )
 
894
            _printer.breakLine();
 
895
        } catch ( IOException except ) {
 
896
            throw new SAXException( except );
 
897
        }
 
898
    }
 
899
 
 
900
 
 
901
    public void externalEntityDecl( String name, String publicId, String systemId )
 
902
        throws SAXException
 
903
    {
 
904
        try {
 
905
        _printer.enterDTD();
 
906
        unparsedEntityDecl( name, publicId, systemId, null );
 
907
        } catch ( IOException except ) {
 
908
            throw new SAXException( except );
 
909
        }
 
910
    }
 
911
 
 
912
 
 
913
    public void unparsedEntityDecl( String name, String publicId,
 
914
                                    String systemId, String notationName )
 
915
        throws SAXException
 
916
    {
 
917
        try {
 
918
        _printer.enterDTD();
 
919
        if ( publicId == null ) {
 
920
            _printer.printText( "<!ENTITY " );
 
921
            _printer.printText( name );
 
922
            _printer.printText( " SYSTEM " );
 
923
            printDoctypeURL( systemId );
 
924
        } else {
 
925
            _printer.printText( "<!ENTITY " );
 
926
            _printer.printText( name );
 
927
            _printer.printText( " PUBLIC " );
 
928
            printDoctypeURL( publicId );
 
929
            _printer.printText( ' ' );
 
930
            printDoctypeURL( systemId );
 
931
        }
 
932
        if ( notationName != null ) {
 
933
            _printer.printText( " NDATA " );
 
934
            _printer.printText( notationName );
 
935
        }
 
936
        _printer.printText( '>' );
 
937
        if ( _indenting )
 
938
            _printer.breakLine();
 
939
        } catch ( IOException except ) {
 
940
            throw new SAXException( except );
 
941
    }
 
942
    }
 
943
 
 
944
 
 
945
    public void notationDecl( String name, String publicId, String systemId )
 
946
        throws SAXException
 
947
    {
 
948
        try {
 
949
        _printer.enterDTD();
 
950
        if ( publicId != null ) {
 
951
            _printer.printText( "<!NOTATION " );
 
952
            _printer.printText( name );
 
953
            _printer.printText( " PUBLIC " );
 
954
            printDoctypeURL( publicId );
 
955
            if ( systemId != null ) {
 
956
                _printer.printText( ' ' );
 
957
                printDoctypeURL( systemId );
 
958
            }
 
959
        } else {
 
960
            _printer.printText( "<!NOTATION " );
 
961
            _printer.printText( name );
 
962
            _printer.printText( " SYSTEM " );
 
963
            printDoctypeURL( systemId );
 
964
        }
 
965
        _printer.printText( '>' );
 
966
        if ( _indenting )
 
967
            _printer.breakLine();
 
968
        } catch ( IOException except ) {
 
969
            throw new SAXException( except );
 
970
        }
 
971
    }
 
972
 
 
973
 
 
974
    //------------------------------------------//
 
975
    // Generic node serializing methods methods //
 
976
    //------------------------------------------//
 
977
 
 
978
 
 
979
    /**
 
980
     * Serialize the DOM node. This method is shared across XML, HTML and XHTML
 
981
     * serializers and the differences are masked out in a separate {@link
 
982
     * #serializeElement}.
 
983
     *
 
984
     * @param node The node to serialize
 
985
     * @see #serializeElement
 
986
     * @throws IOException An I/O exception occured while
 
987
     *   serializing
 
988
     */
 
989
    protected void serializeNode( Node node )
 
990
        throws IOException
 
991
    {
 
992
        fCurrentNode = node;
 
993
 
 
994
        // Based on the node type call the suitable SAX handler.
 
995
        // Only comments entities and documents which are not
 
996
        // handled by SAX are serialized directly.
 
997
        switch ( node.getNodeType() ) {
 
998
        case Node.TEXT_NODE : {
 
999
            String text;
 
1000
 
 
1001
            text = node.getNodeValue();
 
1002
            if ( text != null ) {
 
1003
                if (fDOMFilter !=null && 
 
1004
                    (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_TEXT)!= 0) {
 
1005
                    short code = fDOMFilter.acceptNode(node);
 
1006
                    switch (code) {
 
1007
                        case NodeFilter.FILTER_REJECT:
 
1008
                        case NodeFilter.FILTER_SKIP: { 
 
1009
                            break;
 
1010
                        }
 
1011
                        default: {
 
1012
                            characters(text);
 
1013
                        }
 
1014
                    }
 
1015
                }
 
1016
                else if ( !_indenting || getElementState().preserveSpace
 
1017
                     || (text.replace('\n',' ').trim().length() != 0))
 
1018
                    characters( text );
 
1019
            
 
1020
            }                
 
1021
            break;
 
1022
        }
 
1023
 
 
1024
        case Node.CDATA_SECTION_NODE : {
 
1025
            String text = node.getNodeValue();
 
1026
            if ((features & DOMSerializerImpl.CDATA) != 0) {
 
1027
                if (text != null) {
 
1028
                    if (fDOMFilter != null
 
1029
                        && (fDOMFilter.getWhatToShow()
 
1030
                            & NodeFilter.SHOW_CDATA_SECTION)
 
1031
                            != 0) {
 
1032
                        short code = fDOMFilter.acceptNode(node);
 
1033
                        switch (code) {
 
1034
                            case NodeFilter.FILTER_REJECT :
 
1035
                            case NodeFilter.FILTER_SKIP :
 
1036
                                {
 
1037
                                    // skip the CDATA node
 
1038
                                    return;
 
1039
                                }
 
1040
                            default :
 
1041
                                {
 
1042
                                    //fall through..
 
1043
                                }
 
1044
                        }
 
1045
                    }
 
1046
                    startCDATA();
 
1047
                    characters(text);
 
1048
                    endCDATA();
 
1049
                }
 
1050
            } else {
 
1051
                // transform into a text node
 
1052
                characters(text);
 
1053
            }
 
1054
            break;
 
1055
        }
 
1056
        case Node.COMMENT_NODE : {
 
1057
            String text;
 
1058
 
 
1059
            if ( ! _format.getOmitComments() ) {
 
1060
                text = node.getNodeValue();
 
1061
                if ( text != null ) {
 
1062
                
 
1063
                    if (fDOMFilter !=null && 
 
1064
                          (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_COMMENT)!= 0) {
 
1065
                          short code = fDOMFilter.acceptNode(node);
 
1066
                          switch (code) {
 
1067
                              case NodeFilter.FILTER_REJECT:
 
1068
                              case NodeFilter.FILTER_SKIP: { 
 
1069
                                  // skip the comment node
 
1070
                                  return;
 
1071
                              }
 
1072
                              default: {
 
1073
                                   // fall through
 
1074
                              }
 
1075
                          }                      
 
1076
                    }
 
1077
                    comment( text );
 
1078
                }                    
 
1079
            }
 
1080
            break;
 
1081
        }
 
1082
 
 
1083
        case Node.ENTITY_REFERENCE_NODE : {
 
1084
            Node         child;
 
1085
 
 
1086
            endCDATA();
 
1087
            content();
 
1088
            
 
1089
            if (((features & DOMSerializerImpl.ENTITIES) != 0)
 
1090
                || (node.getFirstChild() == null)) {
 
1091
                if (fDOMFilter !=null && 
 
1092
                      (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ENTITY_REFERENCE)!= 0) {
 
1093
                      short code = fDOMFilter.acceptNode(node);
 
1094
                      switch (code) {
 
1095
                        case NodeFilter.FILTER_REJECT:{
 
1096
                            return; // remove the node
 
1097
                          }
 
1098
                          case NodeFilter.FILTER_SKIP: { 
 
1099
                              child = node.getFirstChild();
 
1100
                              while ( child != null ) {
 
1101
                                  serializeNode( child );
 
1102
                                  child = child.getNextSibling();
 
1103
                              }
 
1104
                              return;
 
1105
                          }
 
1106
 
 
1107
                          default: {
 
1108
                               // fall through
 
1109
                          }
 
1110
                      }
 
1111
                  }         
 
1112
                checkUnboundNamespacePrefixedNode(node);
 
1113
              
 
1114
                _printer.printText("&");
 
1115
                _printer.printText(node.getNodeName());
 
1116
                _printer.printText(";");
 
1117
            }
 
1118
            else {
 
1119
                child = node.getFirstChild();
 
1120
                while ( child != null ) {
 
1121
                    serializeNode( child );
 
1122
                    child = child.getNextSibling();
 
1123
                }
 
1124
            }
 
1125
 
 
1126
            break;
 
1127
        }
 
1128
 
 
1129
        case Node.PROCESSING_INSTRUCTION_NODE : {
 
1130
        
 
1131
            if (fDOMFilter !=null && 
 
1132
                  (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_PROCESSING_INSTRUCTION)!= 0) {
 
1133
                  short code = fDOMFilter.acceptNode(node);
 
1134
                  switch (code) {
 
1135
                    case NodeFilter.FILTER_REJECT:                      
 
1136
                    case NodeFilter.FILTER_SKIP: { 
 
1137
                          return;  // skip this node                      
 
1138
                    }
 
1139
                    default: { // fall through
 
1140
                    }
 
1141
                  }
 
1142
            }
 
1143
            processingInstructionIO( node.getNodeName(), node.getNodeValue() );
 
1144
            break;
 
1145
        }
 
1146
        case Node.ELEMENT_NODE :  {
 
1147
 
 
1148
            if (fDOMFilter !=null && 
 
1149
                  (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ELEMENT)!= 0) {
 
1150
                  short code = fDOMFilter.acceptNode(node);
 
1151
                  switch (code) {
 
1152
                    case NodeFilter.FILTER_REJECT: {
 
1153
                        return;                     
 
1154
                    }
 
1155
                    case NodeFilter.FILTER_SKIP: { 
 
1156
                        Node child = node.getFirstChild();
 
1157
                        while ( child != null ) {
 
1158
                            serializeNode( child );
 
1159
                            child = child.getNextSibling();
 
1160
                        }
 
1161
                        return;  // skip this node                      
 
1162
                    }
 
1163
 
 
1164
                    default: { // fall through
 
1165
                    }
 
1166
                  }
 
1167
            }
 
1168
            serializeElement( (Element) node );
 
1169
            break;
 
1170
        }
 
1171
        case Node.DOCUMENT_NODE : {
 
1172
            DocumentType      docType;
 
1173
            
 
1174
            // If there is a document type, use the SAX events to
 
1175
            // serialize it.
 
1176
            docType = ( (Document) node ).getDoctype();
 
1177
            if (docType != null) {
 
1178
                // DOM Level 2 (or higher)
 
1179
                try {
 
1180
                    String internal;
 
1181
 
 
1182
                    _printer.enterDTD();
 
1183
                    _docTypePublicId = docType.getPublicId();
 
1184
                    _docTypeSystemId = docType.getSystemId();
 
1185
                    internal = docType.getInternalSubset();
 
1186
                    if ( internal != null && internal.length() > 0 )
 
1187
                        _printer.printText( internal );
 
1188
                    endDTD();
 
1189
                }
 
1190
                // DOM Level 1 -- does implementation have methods?
 
1191
                catch (NoSuchMethodError nsme) {
 
1192
                    Class docTypeClass = docType.getClass();
 
1193
 
 
1194
                    String docTypePublicId = null;
 
1195
                    String docTypeSystemId = null;
 
1196
                    try {
 
1197
                        java.lang.reflect.Method getPublicId = docTypeClass.getMethod("getPublicId", (Class[]) null);
 
1198
                        if (getPublicId.getReturnType().equals(String.class)) {
 
1199
                            docTypePublicId = (String)getPublicId.invoke(docType, (Object[]) null);
 
1200
                        }
 
1201
                    }
 
1202
                    catch (Exception e) {
 
1203
                        // ignore
 
1204
                    }
 
1205
                    try {
 
1206
                        java.lang.reflect.Method getSystemId = docTypeClass.getMethod("getSystemId", (Class[]) null);
 
1207
                        if (getSystemId.getReturnType().equals(String.class)) {
 
1208
                            docTypeSystemId = (String)getSystemId.invoke(docType, (Object[]) null);
 
1209
                        }
 
1210
                    }
 
1211
                    catch (Exception e) {
 
1212
                        // ignore
 
1213
                    }
 
1214
                    _printer.enterDTD();
 
1215
                    _docTypePublicId = docTypePublicId;
 
1216
                    _docTypeSystemId = docTypeSystemId;
 
1217
                    endDTD();
 
1218
                }
 
1219
            }
 
1220
            // !! Fall through
 
1221
        }
 
1222
        case Node.DOCUMENT_FRAGMENT_NODE : {
 
1223
            Node         child;
 
1224
 
 
1225
            // By definition this will happen if the node is a document,
 
1226
            // document fragment, etc. Just serialize its contents. It will
 
1227
            // work well for other nodes that we do not know how to serialize.
 
1228
            child = node.getFirstChild();
 
1229
            while ( child != null ) {
 
1230
                serializeNode( child );
 
1231
                child = child.getNextSibling();
 
1232
            }
 
1233
            break;
 
1234
        }
 
1235
 
 
1236
        default:
 
1237
            break;
 
1238
        }
 
1239
    }
 
1240
 
 
1241
 
 
1242
    /**
 
1243
     * Must be called by a method about to print any type of content.
 
1244
     * If the element was just opened, the opening tag is closed and
 
1245
     * will be matched to a closing tag. Returns the current element
 
1246
     * state with <tt>empty</tt> and <tt>afterElement</tt> set to false.
 
1247
     *
 
1248
     * @return The current element state
 
1249
     * @throws IOException An I/O exception occurred while
 
1250
     *   serializing
 
1251
     */
 
1252
    protected ElementState content()
 
1253
        throws IOException
 
1254
    {
 
1255
        ElementState state;
 
1256
 
 
1257
        state = getElementState();
 
1258
        if ( ! isDocumentState() ) {
 
1259
            // Need to close CData section first
 
1260
            if ( state.inCData && ! state.doCData ) {
 
1261
                _printer.printText( "]]>" );
 
1262
                state.inCData = false;
 
1263
            }
 
1264
            // If this is the first content in the element,
 
1265
            // change the state to not-empty and close the
 
1266
            // opening element tag.
 
1267
            if ( state.empty ) {
 
1268
                _printer.printText( '>' );
 
1269
                state.empty = false;
 
1270
            }
 
1271
            // Except for one content type, all of them
 
1272
            // are not last element. That one content
 
1273
            // type will take care of itself.
 
1274
            state.afterElement = false;
 
1275
            // Except for one content type, all of them
 
1276
            // are not last comment. That one content
 
1277
            // type will take care of itself.
 
1278
            state.afterComment = false;
 
1279
        }
 
1280
        return state;
 
1281
    }
 
1282
 
 
1283
 
 
1284
    /**
 
1285
     * Called to print the text contents in the prevailing element format.
 
1286
     * Since this method is capable of printing text as CDATA, it is used
 
1287
     * for that purpose as well. White space handling is determined by the
 
1288
     * current element state. In addition, the output format can dictate
 
1289
     * whether the text is printed as CDATA or unescaped.
 
1290
     *
 
1291
     * @param text The text to print
 
1292
     * @throws IOException An I/O exception occured while
 
1293
     *   serializing
 
1294
     */
 
1295
    protected void characters( String text )
 
1296
        throws IOException
 
1297
    {
 
1298
        ElementState state;
 
1299
 
 
1300
        state = content();
 
1301
        // Check if text should be print as CDATA section or unescaped
 
1302
        // based on elements listed in the output format (the element
 
1303
        // state) or whether we are inside a CDATA section or entity.
 
1304
 
 
1305
        if ( state.inCData || state.doCData ) {
 
1306
            // Print a CDATA section. The text is not escaped, but ']]>'
 
1307
            // appearing in the code must be identified and dealt with.
 
1308
            // The contents of a text node is considered space preserving.
 
1309
            if ( ! state.inCData ) {
 
1310
                _printer.printText("<![CDATA[");
 
1311
                state.inCData = true;
 
1312
            }
 
1313
            int saveIndent = _printer.getNextIndent();
 
1314
            _printer.setNextIndent( 0 );
 
1315
            printCDATAText( text);
 
1316
            _printer.setNextIndent( saveIndent );
 
1317
 
 
1318
        } else {
 
1319
 
 
1320
            int saveIndent;
 
1321
 
 
1322
            if ( state.preserveSpace ) {
 
1323
                // If preserving space then hold of indentation so no
 
1324
                // excessive spaces are printed at line breaks, escape
 
1325
                // the text content without replacing spaces and print
 
1326
                // the text breaking only at line breaks.
 
1327
                saveIndent = _printer.getNextIndent();
 
1328
                _printer.setNextIndent( 0 );
 
1329
                printText( text, true, state.unescaped );
 
1330
                _printer.setNextIndent( saveIndent );
 
1331
            } else {
 
1332
                printText( text, false, state.unescaped );
 
1333
            }
 
1334
        }
 
1335
    }
 
1336
 
 
1337
 
 
1338
    /**
 
1339
     * Returns the suitable entity reference for this character value,
 
1340
     * or null if no such entity exists. Calling this method with <tt>'&amp;'</tt>
 
1341
     * will return <tt>"&amp;amp;"</tt>.
 
1342
     *
 
1343
     * @param ch Character value
 
1344
     * @return Character entity name, or null
 
1345
     */
 
1346
    protected abstract String getEntityRef( int ch );
 
1347
 
 
1348
 
 
1349
    /**
 
1350
     * Called to serializee the DOM element. The element is serialized based on
 
1351
     * the serializer's method (XML, HTML, XHTML).
 
1352
     *
 
1353
     * @param elem The element to serialize
 
1354
     * @throws IOException An I/O exception occured while
 
1355
     *   serializing
 
1356
     */
 
1357
    protected abstract void serializeElement( Element elem )
 
1358
        throws IOException;
 
1359
 
 
1360
 
 
1361
    /**
 
1362
     * Comments and PIs cannot be serialized before the root element,
 
1363
     * because the root element serializes the document type, which
 
1364
     * generally comes first. Instead such PIs and comments are
 
1365
     * accumulated inside a vector and serialized by calling this
 
1366
     * method. Will be called when the root element is serialized
 
1367
     * and when the document finished serializing.
 
1368
     *
 
1369
     * @throws IOException An I/O exception occured while
 
1370
     *   serializing
 
1371
     */
 
1372
    protected void serializePreRoot()
 
1373
        throws IOException
 
1374
    {
 
1375
        int i;
 
1376
 
 
1377
        if ( _preRoot != null ) {
 
1378
            for ( i = 0 ; i < _preRoot.size() ; ++i ) {
 
1379
                printText( (String) _preRoot.elementAt( i ), true, true );
 
1380
                if ( _indenting )
 
1381
                _printer.breakLine();
 
1382
            }
 
1383
            _preRoot.removeAllElements();
 
1384
        }
 
1385
    }
 
1386
 
 
1387
 
 
1388
    //---------------------------------------------//
 
1389
    // Text pretty printing and formatting methods //
 
1390
    //---------------------------------------------//
 
1391
 
 
1392
    protected void printCDATAText( String text ) throws IOException {
 
1393
        int length = text.length();
 
1394
        char ch;
 
1395
 
 
1396
        for ( int index = 0 ; index <  length; ++index ) {
 
1397
            ch = text.charAt( index );            
 
1398
            if (ch == ']'
 
1399
                && index + 2 < length
 
1400
                && text.charAt(index + 1) == ']'
 
1401
                && text.charAt(index + 2) == '>') { // check for ']]>'
 
1402
                if (fDOMErrorHandler != null) {
 
1403
                    // REVISIT: this means that if DOM Error handler is not registered we don't report any
 
1404
                    // fatal errors and might serialize not wellformed document
 
1405
                    if ((features & DOMSerializerImpl.SPLITCDATA) == 0) {
 
1406
                        String msg = DOMMessageFormatter.formatMessage(
 
1407
                            DOMMessageFormatter.SERIALIZER_DOMAIN,
 
1408
                            "EndingCDATA",
 
1409
                            null);    
 
1410
                        if ((features & DOMSerializerImpl.WELLFORMED) != 0) {
 
1411
                            // issue fatal error
 
1412
                            modifyDOMError(msg, DOMError.SEVERITY_FATAL_ERROR, "wf-invalid-character", fCurrentNode);
 
1413
                            fDOMErrorHandler.handleError(fDOMError);
 
1414
                            throw new LSException(LSException.SERIALIZE_ERR, msg);
 
1415
                        }
 
1416
                        // issue error
 
1417
                        modifyDOMError(msg, DOMError.SEVERITY_ERROR, "cdata-section-not-splitted", fCurrentNode);
 
1418
                        if (!fDOMErrorHandler.handleError(fDOMError)) {
 
1419
                            throw new LSException(LSException.SERIALIZE_ERR, msg);
 
1420
                        }                        
 
1421
                    } else {
 
1422
                        // issue warning
 
1423
                        String msg =
 
1424
                            DOMMessageFormatter.formatMessage(
 
1425
                                DOMMessageFormatter.SERIALIZER_DOMAIN,
 
1426
                                "SplittingCDATA",
 
1427
                                null);
 
1428
                        modifyDOMError(
 
1429
                            msg,
 
1430
                            DOMError.SEVERITY_WARNING,
 
1431
                            null, fCurrentNode);
 
1432
                        fDOMErrorHandler.handleError(fDOMError);
 
1433
                    }
 
1434
                }
 
1435
                // split CDATA section
 
1436
                _printer.printText("]]]]><![CDATA[>");
 
1437
                index += 2;
 
1438
                continue;
 
1439
            }
 
1440
            
 
1441
            if (!XMLChar.isValid(ch)) {
 
1442
                // check if it is surrogate
 
1443
                if (++index <length) {
 
1444
                    surrogates(ch, text.charAt(index), true);
 
1445
                } 
 
1446
                else {
 
1447
                    fatalError("The character '"+ch+"' is an invalid XML character"); 
 
1448
                }
 
1449
                continue;
 
1450
            }
 
1451
            if ( ( ch >= ' ' && _encodingInfo.isPrintable(ch) && ch != 0xF7 ) ||
 
1452
                 ch == '\n' || ch == '\r' || ch == '\t' ) {
 
1453
                _printer.printText(ch);
 
1454
            } 
 
1455
            else {
 
1456
 
 
1457
                // The character is not printable -- split CDATA section
 
1458
                _printer.printText("]]>&#x");                        
 
1459
                _printer.printText(Integer.toHexString(ch));                        
 
1460
                _printer.printText(";<![CDATA[");
 
1461
            }
 
1462
        }
 
1463
    }
 
1464
 
 
1465
 
 
1466
    protected void surrogates(int high, int low, boolean inContent) throws IOException{
 
1467
        if (XMLChar.isHighSurrogate(high)) {
 
1468
            if (!XMLChar.isLowSurrogate(low)) {
 
1469
                //Invalid XML
 
1470
                fatalError("The character '"+(char)low+"' is an invalid XML character"); 
 
1471
            }
 
1472
            else {
 
1473
                int supplemental = XMLChar.supplemental((char)high, (char)low);
 
1474
                if (!XMLChar.isValid(supplemental)) {
 
1475
                    //Invalid XML
 
1476
                    fatalError("The character '"+(char)supplemental+"' is an invalid XML character"); 
 
1477
                }
 
1478
                else {
 
1479
                    if (inContent && content().inCData) {
 
1480
                        _printer.printText("]]>&#x");                        
 
1481
                        _printer.printText(Integer.toHexString(supplemental));                        
 
1482
                        _printer.printText(";<![CDATA[");
 
1483
                    }  
 
1484
                    else {
 
1485
                        printHex(supplemental);
 
1486
                    }
 
1487
                }
 
1488
            }
 
1489
        } else {
 
1490
            fatalError("The character '"+(char)high+"' is an invalid XML character"); 
 
1491
        }
 
1492
 
 
1493
    }
 
1494
 
 
1495
    /**
 
1496
     * Called to print additional text with whitespace handling.
 
1497
     * If spaces are preserved, the text is printed as if by calling
 
1498
     * {@link #printText(String,boolean,boolean)} with a call to {@link Printer#breakLine}
 
1499
     * for each new line. If spaces are not preserved, the text is
 
1500
     * broken at space boundaries if longer than the line width;
 
1501
     * Multiple spaces are printed as such, but spaces at beginning
 
1502
     * of line are removed.
 
1503
     *
 
1504
     * @param chars The text to print
 
1505
     * @param start The start offset
 
1506
     * @param length The number of characters
 
1507
     * @param preserveSpace Space preserving flag
 
1508
     * @param unescaped Print unescaped
 
1509
     */
 
1510
    protected void printText( char[] chars, int start, int length,
 
1511
                                    boolean preserveSpace, boolean unescaped )
 
1512
        throws IOException
 
1513
    {
 
1514
 
 
1515
        if ( preserveSpace ) {
 
1516
            // Preserving spaces: the text must print exactly as it is,
 
1517
            // without breaking when spaces appear in the text and without
 
1518
            // consolidating spaces. If a line terminator is used, a line
 
1519
            // break will occur.
 
1520
            while ( length-- > 0 ) {
 
1521
                char ch = chars[ start ];
 
1522
                ++start;
 
1523
                if ( ch == '\n' || ch == '\r' || unescaped ) {
 
1524
                    _printer.printText( ch );
 
1525
                } 
 
1526
                else {
 
1527
                    printEscaped( ch );
 
1528
                }
 
1529
            }
 
1530
        } else {
 
1531
            // Not preserving spaces: print one part at a time, and
 
1532
            // use spaces between parts to break them into different
 
1533
            // lines. Spaces at beginning of line will be stripped
 
1534
            // by printing mechanism. Line terminator is treated
 
1535
            // no different than other text part.
 
1536
            while ( length-- > 0 ) {
 
1537
                char ch = chars[ start ];
 
1538
                ++start;
 
1539
                if ( ch == ' ' || ch == '\f' || ch == '\t' || ch == '\n' || ch == '\r' ) {
 
1540
                    _printer.printSpace();
 
1541
                } 
 
1542
                else if ( unescaped ) {
 
1543
                    _printer.printText( ch );
 
1544
                } 
 
1545
                else {
 
1546
                    printEscaped( ch );
 
1547
                }
 
1548
            }
 
1549
        }
 
1550
    }
 
1551
 
 
1552
 
 
1553
    protected void printText( String text, boolean preserveSpace, boolean unescaped )
 
1554
        throws IOException
 
1555
    {
 
1556
        int index;
 
1557
        char ch;
 
1558
 
 
1559
        if ( preserveSpace ) {
 
1560
            // Preserving spaces: the text must print exactly as it is,
 
1561
            // without breaking when spaces appear in the text and without
 
1562
            // consolidating spaces. If a line terminator is used, a line
 
1563
            // break will occur.
 
1564
            for ( index = 0 ; index < text.length() ; ++index ) {
 
1565
                ch = text.charAt( index );
 
1566
                if ( ch == '\n' || ch == '\r' || unescaped )
 
1567
                    _printer.printText( ch );
 
1568
                else
 
1569
                    printEscaped( ch );
 
1570
            }
 
1571
        } else {
 
1572
            // Not preserving spaces: print one part at a time, and
 
1573
            // use spaces between parts to break them into different
 
1574
            // lines. Spaces at beginning of line will be stripped
 
1575
            // by printing mechanism. Line terminator is treated
 
1576
            // no different than other text part.
 
1577
            for ( index = 0 ; index < text.length() ; ++index ) {
 
1578
                ch = text.charAt( index );
 
1579
                if ( ch == ' ' || ch == '\f' || ch == '\t' || ch == '\n' || ch == '\r' ) {
 
1580
                    _printer.printSpace();
 
1581
                }
 
1582
                else if ( unescaped ) {
 
1583
                    _printer.printText( ch );
 
1584
                }
 
1585
                else {
 
1586
                    printEscaped( ch );
 
1587
                }
 
1588
            }
 
1589
        }
 
1590
    }
 
1591
 
 
1592
 
 
1593
    /**
 
1594
     * Print a document type public or system identifier URL.
 
1595
     * Encapsulates the URL in double quotes, escapes non-printing
 
1596
     * characters and print it equivalent to {@link #printText}.
 
1597
     *
 
1598
     * @param url The document type url to print
 
1599
     */
 
1600
    protected void printDoctypeURL( String url )
 
1601
        throws IOException
 
1602
    {
 
1603
        int                i;
 
1604
 
 
1605
        _printer.printText( '"' );
 
1606
        for( i = 0 ; i < url.length() ; ++i ) {
 
1607
            if ( url.charAt( i ) == '"' ||  url.charAt( i ) < 0x20 || url.charAt( i ) > 0x7F ) {
 
1608
                _printer.printText( '%' );
 
1609
                _printer.printText( Integer.toHexString( url.charAt( i ) ) );
 
1610
            } else
 
1611
                _printer.printText( url.charAt( i ) );
 
1612
        }
 
1613
        _printer.printText( '"' );
 
1614
    }
 
1615
 
 
1616
 
 
1617
    protected void printEscaped( int ch )
 
1618
        throws IOException
 
1619
    {
 
1620
        String charRef;
 
1621
        // If there is a suitable entity reference for this
 
1622
        // character, print it. The list of available entity
 
1623
        // references is almost but not identical between
 
1624
        // XML and HTML.
 
1625
        charRef = getEntityRef( ch );
 
1626
        if ( charRef != null ) {
 
1627
            _printer.printText( '&' );
 
1628
            _printer.printText( charRef );
 
1629
            _printer.printText( ';' );
 
1630
        } else if ( ( ch >= ' ' && _encodingInfo.isPrintable((char)ch) && ch != 0xF7 ) ||
 
1631
                    ch == '\n' || ch == '\r' || ch == '\t' ) {
 
1632
            // Non printables are below ASCII space but not tab or line
 
1633
            // terminator, ASCII delete, or above a certain Unicode threshold.
 
1634
            if (ch < 0x10000) {
 
1635
                _printer.printText((char)ch );
 
1636
            } else {
 
1637
                _printer.printText((char)(((ch-0x10000)>>10)+0xd800));
 
1638
                _printer.printText((char)(((ch-0x10000)&0x3ff)+0xdc00));
 
1639
            }
 
1640
        } else {
 
1641
                        printHex(ch);
 
1642
        }
 
1643
    }
 
1644
    
 
1645
        /**
 
1646
         * Escapes chars
 
1647
         */ 
 
1648
         final void printHex( int ch) throws IOException {      
 
1649
                 _printer.printText( "&#x" );
 
1650
                 _printer.printText(Integer.toHexString(ch));
 
1651
                 _printer.printText( ';' );
 
1652
        
 
1653
         }
 
1654
 
 
1655
 
 
1656
    /**
 
1657
     * Escapes a string so it may be printed as text content or attribute
 
1658
     * value. Non printable characters are escaped using character references.
 
1659
     * Where the format specifies a deault entity reference, that reference
 
1660
     * is used (e.g. <tt>&amp;lt;</tt>).
 
1661
     *
 
1662
     * @param source The string to escape
 
1663
     */
 
1664
    protected void printEscaped( String source )
 
1665
        throws IOException
 
1666
    {
 
1667
        for ( int i = 0 ; i < source.length() ; ++i ) {
 
1668
            int ch = source.charAt(i);
 
1669
            if ((ch & 0xfc00) == 0xd800 && i+1 < source.length()) {
 
1670
                int lowch = source.charAt(i+1);
 
1671
                if ((lowch & 0xfc00) == 0xdc00) {
 
1672
                    ch = 0x10000 + ((ch-0xd800)<<10) + lowch-0xdc00;
 
1673
                    i++;
 
1674
                }
 
1675
            }
 
1676
            printEscaped(ch);
 
1677
        }
 
1678
    }
 
1679
 
 
1680
 
 
1681
    //--------------------------------//
 
1682
    // Element state handling methods //
 
1683
    //--------------------------------//
 
1684
 
 
1685
 
 
1686
    /**
 
1687
     * Return the state of the current element.
 
1688
     *
 
1689
     * @return Current element state
 
1690
     */
 
1691
    protected ElementState getElementState()
 
1692
    {
 
1693
        return _elementStates[ _elementStateCount ];
 
1694
    }
 
1695
 
 
1696
 
 
1697
    /**
 
1698
     * Enter a new element state for the specified element.
 
1699
     * Tag name and space preserving is specified, element
 
1700
     * state is initially empty.
 
1701
     *
 
1702
     * @return Current element state, or null
 
1703
     */
 
1704
    protected ElementState enterElementState( String namespaceURI, String localName,
 
1705
                                              String rawName, boolean preserveSpace )
 
1706
    {
 
1707
        ElementState state;
 
1708
 
 
1709
        if ( _elementStateCount + 1 == _elementStates.length ) {
 
1710
            ElementState[] newStates;
 
1711
 
 
1712
            // Need to create a larger array of states. This does not happen
 
1713
            // often, unless the document is really deep.
 
1714
            newStates = new ElementState[ _elementStates.length + 10 ];
 
1715
            for ( int i = 0 ; i < _elementStates.length ; ++i )
 
1716
                newStates[ i ] = _elementStates[ i ];
 
1717
            for ( int i = _elementStates.length ; i < newStates.length ; ++i )
 
1718
                newStates[ i ] = new ElementState();
 
1719
            _elementStates = newStates;
 
1720
        }
 
1721
 
 
1722
        ++_elementStateCount;
 
1723
        state = _elementStates[ _elementStateCount ];
 
1724
        state.namespaceURI = namespaceURI;
 
1725
        state.localName = localName;
 
1726
        state.rawName = rawName;
 
1727
        state.preserveSpace = preserveSpace;
 
1728
        state.empty = true;
 
1729
        state.afterElement = false;
 
1730
        state.afterComment = false;
 
1731
        state.doCData = state.inCData = false;
 
1732
        state.unescaped = false;
 
1733
        state.prefixes = _prefixes;
 
1734
 
 
1735
        _prefixes = null;
 
1736
        return state;
 
1737
    }
 
1738
 
 
1739
 
 
1740
    /**
 
1741
     * Leave the current element state and return to the
 
1742
     * state of the parent element. If this was the root
 
1743
     * element, return to the state of the document.
 
1744
     *                                                                             
 
1745
     * @return Previous element state
 
1746
     */
 
1747
    protected ElementState leaveElementState()
 
1748
    {
 
1749
        if ( _elementStateCount > 0 ) {
 
1750
            /*Corrected by David Blondeau (blondeau@intalio.com)*/
 
1751
            _prefixes = null;
 
1752
            //_prefixes = _elementStates[ _elementStateCount ].prefixes;
 
1753
            -- _elementStateCount;
 
1754
            return _elementStates[ _elementStateCount ];
 
1755
        }
 
1756
        String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "Internal", null);
 
1757
        throw new IllegalStateException(msg);
 
1758
    }
 
1759
 
 
1760
 
 
1761
    /**
 
1762
     * Returns true if in the state of the document.
 
1763
     * Returns true before entering any element and after
 
1764
     * leaving the root element.
 
1765
     *
 
1766
     * @return True if in the state of the document
 
1767
     */
 
1768
    protected boolean isDocumentState() {
 
1769
        return _elementStateCount == 0;
 
1770
    }
 
1771
    
 
1772
    /** Clears document state. **/
 
1773
    final void clearDocumentState() {
 
1774
        _elementStateCount = 0;
 
1775
    }
 
1776
 
 
1777
    /**
 
1778
     * Returns the namespace prefix for the specified URI.
 
1779
     * If the URI has been mapped to a prefix, returns the
 
1780
     * prefix, otherwise returns null.
 
1781
     *
 
1782
     * @param namespaceURI The namespace URI
 
1783
     * @return The namespace prefix if known, or null
 
1784
     */
 
1785
    protected String getPrefix( String namespaceURI )
 
1786
    {
 
1787
        String    prefix;
 
1788
 
 
1789
        if ( _prefixes != null ) {
 
1790
            prefix = (String) _prefixes.get( namespaceURI );
 
1791
            if ( prefix != null )
 
1792
                return prefix;
 
1793
        }
 
1794
        if ( _elementStateCount == 0 ) {
 
1795
            return null;
 
1796
        }
 
1797
        for ( int i = _elementStateCount ; i > 0 ; --i ) {
 
1798
            if ( _elementStates[ i ].prefixes != null ) {
 
1799
                prefix = (String) _elementStates[ i ].prefixes.get( namespaceURI );
 
1800
                if ( prefix != null )
 
1801
                    return prefix;
 
1802
            }
 
1803
        }
 
1804
        return null;
 
1805
    }
 
1806
 
 
1807
    /**
 
1808
     * The method modifies global DOM error object
 
1809
     * 
 
1810
     * @param message
 
1811
     * @param severity
 
1812
     * @param type
 
1813
     * @return a DOMError
 
1814
     */
 
1815
    protected DOMError modifyDOMError(String message, short severity, String type, Node node){
 
1816
            fDOMError.reset();
 
1817
            fDOMError.fMessage = message;
 
1818
            fDOMError.fType = type;
 
1819
            fDOMError.fSeverity = severity;
 
1820
            fDOMError.fLocator = new DOMLocatorImpl(-1, -1, -1, node, null);
 
1821
            return fDOMError;
 
1822
        
 
1823
    }
 
1824
 
 
1825
 
 
1826
    protected void fatalError(String message) throws IOException{
 
1827
        if (fDOMErrorHandler != null) {
 
1828
            modifyDOMError(message, DOMError.SEVERITY_FATAL_ERROR, null, fCurrentNode);
 
1829
            fDOMErrorHandler.handleError(fDOMError);
 
1830
        } 
 
1831
        else {
 
1832
            throw new IOException(message);
 
1833
        }
 
1834
    }
 
1835
    
 
1836
        /**
 
1837
         * DOM level 3: 
 
1838
         * Check a node to determine if it contains unbound namespace prefixes.
 
1839
         *
 
1840
         * @param node The node to check for unbound namespace prefices
 
1841
         */
 
1842
         protected void checkUnboundNamespacePrefixedNode (Node node) throws IOException{
 
1843
                
 
1844
         }
 
1845
}