1 /*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5 /*
6 * Copyright 1999-2002,2004,2005 The Apache Software Foundation.
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21
22
23 // Sep 14, 2000:
24 // Fixed problem with namespace handling. Contributed by
25 // David Blondeau <blondeau@intalio.com>
26 // Sep 14, 2000:
27 // Fixed serializer to report IO exception directly, instead at
28 // the end of document processing.
29 // Reported by Patrick Higgins <phiggins@transzap.com>
30 // Aug 21, 2000:
31 // Fixed bug in startDocument not calling prepare.
32 // Reported by Mikael Staldal <d96-mst-ingen-reklam@d.kth.se>
33 // Aug 21, 2000:
34 // Added ability to omit DOCTYPE declaration.
35
36
37 package com.sun.org.apache.xml.internal.serialize;
38
39
40 import java.io.IOException;
41 import java.io.OutputStream;
42 import java.io.Writer;
43 import java.util.Enumeration;
44
45 import com.sun.org.apache.xerces.internal.dom.DOMMessageFormatter;
46 import com.sun.org.apache.xerces.internal.util.NamespaceSupport;
47 import com.sun.org.apache.xerces.internal.util.SymbolTable;
48 import com.sun.org.apache.xerces.internal.util.XMLChar;
49 import com.sun.org.apache.xerces.internal.util.XMLSymbols;
50 import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
51 import org.w3c.dom.Attr;
52 import org.w3c.dom.DOMError;
53 import org.w3c.dom.Element;
54 import org.w3c.dom.NamedNodeMap;
55 import org.w3c.dom.Node;
56 import org.w3c.dom.traversal.NodeFilter;
57 import org.xml.sax.AttributeList;
58 import org.xml.sax.Attributes;
59 import org.xml.sax.SAXException;
60 import org.xml.sax.helpers.AttributesImpl;
61
62 /**
63 * Implements an XML serializer supporting both DOM and SAX pretty
64 * serializing. For usage instructions see {@link Serializer}.
65 * <p>
66 * If an output stream is used, the encoding is taken from the
67 * output format (defaults to <tt>UTF-8</tt>). If a writer is
68 * used, make sure the writer uses the same encoding (if applies)
69 * as specified in the output format.
70 * <p>
71 * The serializer supports both DOM and SAX. SAX serializing is done by firing
72 * SAX events and using the serializer as a document handler. DOM serializing is done
73 * by calling {@link #serialize(Document)} or by using DOM Level 3
74 * {@link org.w3c.dom.ls.DOMSerializer} and
75 * serializing with {@link org.w3c.dom.ls.DOMSerializer#write},
76 * {@link org.w3c.dom.ls.DOMSerializer#writeToString}.
77 * <p>
78 * If an I/O exception occurs while serializing, the serializer
79 * will not throw an exception directly, but only throw it
80 * at the end of serializing (either DOM or SAX's {@link
81 * org.xml.sax.DocumentHandler#endDocument}.
82 * <p>
83 * For elements that are not specified as whitespace preserving,
84 * the serializer will potentially break long text lines at space
85 * boundaries, indent lines, and serialize elements on separate
86 * lines. Line terminators will be regarded as spaces, and
87 * spaces at beginning of line will be stripped.
88 * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
89 * @author <a href="mailto:rahul.srivastava@sun.com">Rahul Srivastava</a>
90 * @author Elena Litani IBM
91 * @see Serializer
92 */
93 public class XMLSerializer
94 extends BaseMarkupSerializer {
95
96 //
97 // constants
98 //
99
100 protected static final boolean DEBUG = false;
101
102 //
103 // data
104 //
105
106 //
107 // DOM Level 3 implementation: variables intialized in DOMSerializerImpl
108 //
109
110 /** stores namespaces in scope */
111 protected NamespaceSupport fNSBinder;
112
113 /** stores all namespace bindings on the current element */
114 protected NamespaceSupport fLocalNSBinder;
115
116 /** symbol table for serialization */
117 protected SymbolTable fSymbolTable;
118
119 protected final static String PREFIX = "NS";
120
121 /**
122 * Controls whether namespace fixup should be performed during
123 * the serialization.
124 * NOTE: if this field is set to true the following
125 * fields need to be initialized: fNSBinder, fLocalNSBinder, fSymbolTable,
126 * XMLSymbols.EMPTY_STRING, fXmlSymbol, fXmlnsSymbol
127 */
128 protected boolean fNamespaces = false;
129
130 /**
131 * Controls whether namespace prefixes will be printed out during serialization
132 */
133 protected boolean fNamespacePrefixes = true;
134
135
136 private boolean fPreserveSpace;
137
138
139 /**
140 * Constructs a new serializer. The serializer cannot be used without
141 * calling {@link #setOutputCharStream} or {@link #setOutputByteStream}
142 * first.
143 */
144 public XMLSerializer() {
145 super( new OutputFormat( Method.XML, null, false ) );
146 }
147
148
149 /**
150 * Constructs a new serializer. The serializer cannot be used without
151 * calling {@link #setOutputCharStream} or {@link #setOutputByteStream}
152 * first.
153 */
154 public XMLSerializer( OutputFormat format ) {
155 super( format != null ? format : new OutputFormat( Method.XML, null, false ) );
156 _format.setMethod( Method.XML );
157 }
158
159
160 /**
161 * Constructs a new serializer that writes to the specified writer
162 * using the specified output format. If <tt>format</tt> is null,
163 * will use a default output format.
164 *
165 * @param writer The writer to use
166 * @param format The output format to use, null for the default
167 */
168 public XMLSerializer( Writer writer, OutputFormat format ) {
169 super( format != null ? format : new OutputFormat( Method.XML, null, false ) );
170 _format.setMethod( Method.XML );
171 setOutputCharStream( writer );
172 }
173
174
175 /**
176 * Constructs a new serializer that writes to the specified output
177 * stream using the specified output format. If <tt>format</tt>
178 * is null, will use a default output format.
179 *
180 * @param output The output stream to use
181 * @param format The output format to use, null for the default
182 */
183 public XMLSerializer( OutputStream output, OutputFormat format ) {
184 super( format != null ? format : new OutputFormat( Method.XML, null, false ) );
185 _format.setMethod( Method.XML );
186 setOutputByteStream( output );
187 }
188
189
190 public void setOutputFormat( OutputFormat format ) {
191 super.setOutputFormat( format != null ? format : new OutputFormat( Method.XML, null, false ) );
192 }
193
194
195 /**
196 * This methods turns on namespace fixup algorithm during
197 * DOM serialization.
198 * @see org.w3c.dom.ls.DOMSerializer
199 *
200 * @param namespaces
201 */
202 public void setNamespaces (boolean namespaces){
203 fNamespaces = namespaces;
204 if (fNSBinder == null) {
205 fNSBinder = new NamespaceSupport();
206 fLocalNSBinder = new NamespaceSupport();
207 fSymbolTable = new SymbolTable();
208 }
209 }
210
211 //-----------------------------------------//
212 // SAX content handler serializing methods //
213 //-----------------------------------------//
214
215
216 public void startElement( String namespaceURI, String localName,
217 String rawName, Attributes attrs )
218 throws SAXException
219 {
220 int i;
221 boolean preserveSpace;
222 ElementState state;
223 String name;
224 String value;
225 boolean addNSAttr = false;
226
227 if (DEBUG) {
228 System.out.println("==>startElement("+namespaceURI+","+localName+
229 ","+rawName+")");
230 }
231
232 try {
233 if (_printer == null) {
234 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "NoWriterSupplied", null);
235 throw new IllegalStateException(msg);
236 }
237
238 state = getElementState();
239 if (isDocumentState()) {
240 // If this is the root element handle it differently.
241 // If the first root element in the document, serialize
242 // the document's DOCTYPE. Space preserving defaults
243 // to that of the output format.
244 if (! _started)
245 startDocument( ( localName == null || localName.length() == 0 ) ? rawName : localName );
246 } else {
247 // For any other element, if first in parent, then
248 // close parent's opening tag and use the parnet's
249 // space preserving.
250 if (state.empty)
251 _printer.printText( '>' );
252 // Must leave CData section first
253 if (state.inCData) {
254 _printer.printText( "]]>" );
255 state.inCData = false;
256 }
257 // Indent this element on a new line if the first
258 // content of the parent element or immediately
259 // following an element or a comment
260 if (_indenting && ! state.preserveSpace &&
261 ( state.empty || state.afterElement || state.afterComment))
262 _printer.breakLine();
263 }
264 preserveSpace = state.preserveSpace;
265
266 //We remove the namespaces from the attributes list so that they will
267 //be in _prefixes
268 attrs = extractNamespaces(attrs);
269
270 // Do not change the current element state yet.
271 // This only happens in endElement().
272 if (rawName == null || rawName.length() == 0) {
273 if (localName == null) {
274 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "NoName", null);
275 throw new SAXException(msg);
276 }
277 if (namespaceURI != null && ! namespaceURI.equals( "" )) {
278 String prefix;
279 prefix = getPrefix( namespaceURI );
280 if (prefix != null && prefix.length() > 0)
281 rawName = prefix + ":" + localName;
282 else
283 rawName = localName;
284 } else
285 rawName = localName;
286 addNSAttr = true;
287 }
288
289 _printer.printText( '<' );
290 _printer.printText( rawName );
291 _printer.indent();
292
293 // For each attribute print it's name and value as one part,
294 // separated with a space so the element can be broken on
295 // multiple lines.
296 if (attrs != null) {
297 for (i = 0 ; i < attrs.getLength() ; ++i) {
298 _printer.printSpace();
299
300 name = attrs.getQName( i );
301 if (name != null && name.length() == 0) {
302 String prefix;
303 String attrURI;
304
305 name = attrs.getLocalName( i );
306 attrURI = attrs.getURI( i );
307 if (( attrURI != null && attrURI.length() != 0 ) &&
308 ( namespaceURI == null || namespaceURI.length() == 0 ||
309 ! attrURI.equals( namespaceURI ) )) {
310 prefix = getPrefix( attrURI );
311 if (prefix != null && prefix.length() > 0)
312 name = prefix + ":" + name;
313 }
314 }
315
316 value = attrs.getValue( i );
317 if (value == null)
318 value = "";
319 _printer.printText( name );
320 _printer.printText( "=\"" );
321 printEscaped( value );
322 _printer.printText( '"' );
323
324 // If the attribute xml:space exists, determine whether
325 // to preserve spaces in this and child nodes based on
326 // its value.
327 if (name.equals( "xml:space" )) {
328 if (value.equals( "preserve" ))
329 preserveSpace = true;
330 else
331 preserveSpace = _format.getPreserveSpace();
332 }
333 }
334 }
335
336 if (_prefixes != null) {
337 Enumeration keys;
338
339 keys = _prefixes.keys();
340 while (keys.hasMoreElements()) {
341 _printer.printSpace();
342 value = (String) keys.nextElement();
343 name = (String) _prefixes.get( value );
344 if (name.length() == 0) {
345 _printer.printText( "xmlns=\"" );
346 printEscaped( value );
347 _printer.printText( '"' );
348 } else {
349 _printer.printText( "xmlns:" );
350 _printer.printText( name );
351 _printer.printText( "=\"" );
352 printEscaped( value );
353 _printer.printText( '"' );
354 }
355 }
356 }
357
358 // Now it's time to enter a new element state
359 // with the tag name and space preserving.
360 // We still do not change the curent element state.
361 state = enterElementState( namespaceURI, localName, rawName, preserveSpace );
362 name = ( localName == null || localName.length() == 0 ) ? rawName : namespaceURI + "^" + localName;
363 state.doCData = _format.isCDataElement( name );
364 state.unescaped = _format.isNonEscapingElement( name );
365 } catch (IOException except) {
366 throw new SAXException( except );
367 }
368 }
369
370
371 public void endElement( String namespaceURI, String localName,
372 String rawName )
373 throws SAXException
374 {
375 try {
376 endElementIO( namespaceURI, localName, rawName );
377 } catch (IOException except) {
378 throw new SAXException( except );
379 }
380 }
381
382
383 public void endElementIO( String namespaceURI, String localName,
384 String rawName )
385 throws IOException
386 {
387 ElementState state;
388 if (DEBUG) {
389 System.out.println("==>endElement: " +rawName);
390 }
391 // Works much like content() with additions for closing
392 // an element. Note the different checks for the closed
393 // element's state and the parent element's state.
394 _printer.unindent();
395 state = getElementState();
396 if (state.empty) {
397 _printer.printText( "/>" );
398 } else {
399 // Must leave CData section first
400 if (state.inCData)
401 _printer.printText( "]]>" );
402 // This element is not empty and that last content was
403 // another element, so print a line break before that
404 // last element and this element's closing tag.
405 if (_indenting && ! state.preserveSpace && (state.afterElement || state.afterComment))
406 _printer.breakLine();
407 _printer.printText( "</" );
408 _printer.printText( state.rawName );
409 _printer.printText( '>' );
410 }
411 // Leave the element state and update that of the parent
412 // (if we're not root) to not empty and after element.
413 state = leaveElementState();
414 state.afterElement = true;
415 state.afterComment = false;
416 state.empty = false;
417 if (isDocumentState())
418 _printer.flush();
419 }
420
421
422 //------------------------------------------//
423 // SAX document handler serializing methods //
424 //------------------------------------------//
425
426
427 public void startElement( String tagName, AttributeList attrs )
428 throws SAXException
429 {
430 int i;
431 boolean preserveSpace;
432 ElementState state;
433 String name;
434 String value;
435
436
437 if (DEBUG) {
438 System.out.println("==>startElement("+tagName+")");
439 }
440
441 try {
442 if (_printer == null) {
443 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "NoWriterSupplied", null);
444 throw new IllegalStateException(msg);
445 }
446
447 state = getElementState();
448 if (isDocumentState()) {
449 // If this is the root element handle it differently.
450 // If the first root element in the document, serialize
451 // the document's DOCTYPE. Space preserving defaults
452 // to that of the output format.
453 if (! _started)
454 startDocument( tagName );
455 } else {
456 // For any other element, if first in parent, then
457 // close parent's opening tag and use the parnet's
458 // space preserving.
459 if (state.empty)
460 _printer.printText( '>' );
461 // Must leave CData section first
462 if (state.inCData) {
463 _printer.printText( "]]>" );
464 state.inCData = false;
465 }
466 // Indent this element on a new line if the first
467 // content of the parent element or immediately
468 // following an element.
469 if (_indenting && ! state.preserveSpace &&
470 ( state.empty || state.afterElement || state.afterComment))
471 _printer.breakLine();
472 }
473 preserveSpace = state.preserveSpace;
474
475 // Do not change the current element state yet.
476 // This only happens in endElement().
477
478 _printer.printText( '<' );
479 _printer.printText( tagName );
480 _printer.indent();
481
482 // For each attribute print it's name and value as one part,
483 // separated with a space so the element can be broken on
484 // multiple lines.
485 if (attrs != null) {
486 for (i = 0 ; i < attrs.getLength() ; ++i) {
487 _printer.printSpace();
488 name = attrs.getName( i );
489 value = attrs.getValue( i );
490 if (value != null) {
491 _printer.printText( name );
492 _printer.printText( "=\"" );
493 printEscaped( value );
494 _printer.printText( '"' );
495 }
496
497 // If the attribute xml:space exists, determine whether
498 // to preserve spaces in this and child nodes based on
499 // its value.
500 if (name.equals( "xml:space" )) {
501 if (value.equals( "preserve" ))
502 preserveSpace = true;
503 else
504 preserveSpace = _format.getPreserveSpace();
505 }
506 }
507 }
508 // Now it's time to enter a new element state
509 // with the tag name and space preserving.
510 // We still do not change the curent element state.
511 state = enterElementState( null, null, tagName, preserveSpace );
512 state.doCData = _format.isCDataElement( tagName );
513 state.unescaped = _format.isNonEscapingElement( tagName );
514 } catch (IOException except) {
515 throw new SAXException( except );
516 }
517
518 }
519
520
521 public void endElement( String tagName )
522 throws SAXException
523 {
524 endElement( null, null, tagName );
525 }
526
527
528
529 //------------------------------------------//
530 // Generic node serializing methods methods //
531 //------------------------------------------//
532
533
534 /**
535 * Called to serialize the document's DOCTYPE by the root element.
536 * The document type declaration must name the root element,
537 * but the root element is only known when that element is serialized,
538 * and not at the start of the document.
539 * <p>
540 * This method will check if it has not been called before ({@link #_started}),
541 * will serialize the document type declaration, and will serialize all
542 * pre-root comments and PIs that were accumulated in the document
543 * (see {@link #serializePreRoot}). Pre-root will be serialized even if
544 * this is not the first root element of the document.
545 */
546 protected void startDocument( String rootTagName )
547 throws IOException
548 {
549 int i;
550 String dtd;
551
552 dtd = _printer.leaveDTD();
553 if (! _started) {
554
555 if (! _format.getOmitXMLDeclaration()) {
556 StringBuffer buffer;
557
558 // Serialize the document declaration appreaing at the head
559 // of very XML document (unless asked not to).
560 buffer = new StringBuffer( "<?xml version=\"" );
561 if (_format.getVersion() != null)
562 buffer.append( _format.getVersion() );
563 else
564 buffer.append( "1.0" );
565 buffer.append( '"' );
566 String format_encoding = _format.getEncoding();
567 if (format_encoding != null) {
568 buffer.append( " encoding=\"" );
569 buffer.append( format_encoding );
570 buffer.append( '"' );
571 }
572 if (_format.getStandalone() && _docTypeSystemId == null &&
573 _docTypePublicId == null)
574 buffer.append( " standalone=\"yes\"" );
575 buffer.append( "?>" );
576 _printer.printText( buffer );
577 _printer.breakLine();
578 }
579
580 if (! _format.getOmitDocumentType()) {
581 if (_docTypeSystemId != null) {
582 // System identifier must be specified to print DOCTYPE.
583 // If public identifier is specified print 'PUBLIC
584 // <public> <system>', if not, print 'SYSTEM <system>'.
585 _printer.printText( "<!DOCTYPE " );
586 _printer.printText( rootTagName );
587 if (_docTypePublicId != null) {
588 _printer.printText( " PUBLIC " );
589 printDoctypeURL( _docTypePublicId );
590 if (_indenting) {
591 _printer.breakLine();
592 for (i = 0 ; i < 18 + rootTagName.length() ; ++i)
593 _printer.printText( " " );
594 } else
595 _printer.printText( " " );
596 printDoctypeURL( _docTypeSystemId );
597 } else {
598 _printer.printText( " SYSTEM " );
599 printDoctypeURL( _docTypeSystemId );
600 }
601
602 // If we accumulated any DTD contents while printing.
603 // this would be the place to print it.
604 if (dtd != null && dtd.length() > 0) {
605 _printer.printText( " [" );
606 printText( dtd, true, true );
607 _printer.printText( ']' );
608 }
609
610 _printer.printText( ">" );
611 _printer.breakLine();
612 } else if (dtd != null && dtd.length() > 0) {
613 _printer.printText( "<!DOCTYPE " );
614 _printer.printText( rootTagName );
615 _printer.printText( " [" );
616 printText( dtd, true, true );
617 _printer.printText( "]>" );
618 _printer.breakLine();
619 }
620 }
621 }
622 _started = true;
623 // Always serialize these, even if not te first root element.
624 serializePreRoot();
625 }
626
627
628 /**
629 * Called to serialize a DOM element. Equivalent to calling {@link
630 * #startElement}, {@link #endElement} and serializing everything
631 * inbetween, but better optimized.
632 */
633 protected void serializeElement( Element elem )
634 throws IOException
635 {
636 Attr attr;
637 NamedNodeMap attrMap;
638 int i;
639 Node child;
640 ElementState state;
641 String name;
642 String value;
643 String tagName;
644
645 String prefix, localUri;
646 String uri;
647 if (fNamespaces) {
648 // local binder stores namespace declaration
649 // that has been printed out during namespace fixup of
650 // the current element
651 fLocalNSBinder.reset();
652
653 // add new namespace context
654 fNSBinder.pushContext();
655 }
656
657 if (DEBUG) {
658 System.out.println("==>startElement: " +elem.getNodeName() +" ns="+elem.getNamespaceURI());
659 }
660 tagName = elem.getTagName();
661 state = getElementState();
662 if (isDocumentState()) {
663 // If this is the root element handle it differently.
664 // If the first root element in the document, serialize
665 // the document's DOCTYPE. Space preserving defaults
666 // to that of the output format.
667
668 if (! _started) {
669 startDocument( tagName);
670 }
671 } else {
672 // For any other element, if first in parent, then
673 // close parent's opening tag and use the parent's
674 // space preserving.
675 if (state.empty)
676 _printer.printText( '>' );
677 // Must leave CData section first
678 if (state.inCData) {
679 _printer.printText( "]]>" );
680 state.inCData = false;
681 }
682 // Indent this element on a new line if the first
683 // content of the parent element or immediately
684 // following an element.
685 if (_indenting && ! state.preserveSpace &&
686 ( state.empty || state.afterElement || state.afterComment))
687 _printer.breakLine();
688 }
689
690 // Do not change the current element state yet.
691 // This only happens in endElement().
692 fPreserveSpace = state.preserveSpace;
693
694
695 int length = 0;
696 attrMap = null;
697 // retrieve attributes
698 if (elem.hasAttributes()) {
699 attrMap = elem.getAttributes();
700 length = attrMap.getLength();
701 }
702
703 if (!fNamespaces) { // no namespace fixup should be performed
704
705 // serialize element name
706 _printer.printText( '<' );
707 _printer.printText( tagName );
708 _printer.indent();
709
710 // For each attribute print it's name and value as one part,
711 // separated with a space so the element can be broken on
712 // multiple lines.
713 for ( i = 0 ; i < length ; ++i ) {
714 attr = (Attr) attrMap.item( i );
715 name = attr.getName();
716 value = attr.getValue();
717 if ( value == null )
718 value = "";
719 printAttribute (name, value, attr.getSpecified(), attr);
720 }
721 } else { // do namespace fixup
722
723 // REVISIT: some optimization could probably be done to avoid traversing
724 // attributes twice.
725 //
726
727 // ---------------------------------------
728 // record all valid namespace declarations
729 // before attempting to fix element's namespace
730 // ---------------------------------------
731
732 for (i = 0;i < length;i++) {
733
734 attr = (Attr) attrMap.item( i );
735 uri = attr.getNamespaceURI();
736 // check if attribute is a namespace decl
737 if (uri != null && uri.equals(NamespaceContext.XMLNS_URI)) {
738
739 value = attr.getNodeValue();
740 if (value == null) {
741 value=XMLSymbols.EMPTY_STRING;
742 }
743
744 if (value.equals(NamespaceContext.XMLNS_URI)) {
745 if (fDOMErrorHandler != null) {
746 String msg = DOMMessageFormatter.formatMessage(
747 DOMMessageFormatter.XML_DOMAIN,"CantBindXMLNS",null );
748 modifyDOMError(msg, DOMError.SEVERITY_ERROR, null, attr);
749 boolean continueProcess = fDOMErrorHandler.handleError(fDOMError);
750 if (!continueProcess) {
751 // stop the namespace fixup and validation
752 throw new RuntimeException(
753 DOMMessageFormatter.formatMessage(
754 DOMMessageFormatter.SERIALIZER_DOMAIN,
755 "SerializationStopped", null));
756 }
757 }
758 } else {
759 prefix = attr.getPrefix();
760 prefix = (prefix == null ||
761 prefix.length() == 0) ? XMLSymbols.EMPTY_STRING :fSymbolTable.addSymbol(prefix);
762 String localpart = fSymbolTable.addSymbol( attr.getLocalName());
763 if (prefix == XMLSymbols.PREFIX_XMLNS) { //xmlns:prefix
764 value = fSymbolTable.addSymbol(value);
765 // record valid decl
766 if (value.length() != 0) {
767 fNSBinder.declarePrefix(localpart, value);
768 } else {
769 // REVISIT: issue error on invalid declarations
770 // xmlns:foo = ""
771 }
772 continue;
773 } else { // xmlns
774 // empty prefix is always bound ("" or some string)
775
776 value = fSymbolTable.addSymbol(value);
777 fNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, value);
778 continue;
779 }
780 } // end-else: valid declaration
781 } // end-if: namespace declaration
782 } // end-for
783
784 //-----------------------
785 // get element uri/prefix
786 //-----------------------
787 uri = elem.getNamespaceURI();
788 prefix = elem.getPrefix();
789
790 //----------------------
791 // output element name
792 //----------------------
793 // REVISIT: this could be removed if we always convert empty string to null
794 // for the namespaces.
795 if ((uri !=null && prefix !=null ) && uri.length() == 0 && prefix.length()!=0) {
796 // uri is an empty string and element has some prefix
797 // the namespace alg later will fix up the namespace attributes
798 // remove element prefix
799 prefix = null;
800 _printer.printText( '<' );
801 _printer.printText( elem.getLocalName() );
802 _printer.indent();
803 } else {
804 _printer.printText( '<' );
805 _printer.printText( tagName );
806 _printer.indent();
807 }
808
809
810 // ---------------------------------------------------------
811 // Fix up namespaces for element: per DOM L3
812 // Need to consider the following cases:
813 //
814 // case 1: <foo:elem xmlns:ns1="myURI" xmlns="default"/>
815 // Assume "foo", "ns1" are declared on the parent. We should not miss
816 // redeclaration for both "ns1" and default namespace. To solve this
817 // we add a local binder that stores declaration only for current element.
818 // This way we avoid outputing duplicate declarations for the same element
819 // as well as we are not omitting redeclarations.
820 //
821 // case 2: <elem xmlns="" xmlns="default"/>
822 // We need to bind default namespace to empty string, to be able to
823 // omit duplicate declarations for the same element
824 //
825 // case 3: <xsl:stylesheet xmlns:xsl="http://xsl">
826 // We create another element body bound to the "http://xsl" namespace
827 // as well as namespace attribute rebounding xsl to another namespace.
828 // <xsl:body xmlns:xsl="http://another">
829 // Need to make sure that the new namespace decl value is changed to
830 // "http://xsl"
831 //
832 // ---------------------------------------------------------
833 // check if prefix/namespace is correct for current element
834 // ---------------------------------------------------------
835
836
837 if (uri != null) { // Element has a namespace
838 uri = fSymbolTable.addSymbol(uri);
839 prefix = (prefix == null ||
840 prefix.length() == 0) ? XMLSymbols.EMPTY_STRING :fSymbolTable.addSymbol(prefix);
841 if (fNSBinder.getURI(prefix) == uri) {
842 // The xmlns:prefix=namespace or xmlns="default" was declared at parent.
843 // The binder always stores mapping of empty prefix to "".
844 // (NOTE: local binder does not store this kind of binding!)
845 // Thus the case where element was declared with uri="" (with or without a prefix)
846 // will be covered here.
847
848 } else {
849 // the prefix is either undeclared
850 // or
851 // conflict: the prefix is bound to another URI
852 if (fNamespacePrefixes) {
853 printNamespaceAttr(prefix, uri);
854 }
855 fLocalNSBinder.declarePrefix(prefix, uri);
856 fNSBinder.declarePrefix(prefix, uri);
857 }
858 } else { // Element has no namespace
859 if (elem.getLocalName() == null) {
860 // DOM Level 1 node!
861 if (fDOMErrorHandler != null) {
862 String msg = DOMMessageFormatter.formatMessage(
863 DOMMessageFormatter.DOM_DOMAIN, "NullLocalElementName",
864 new Object[]{elem.getNodeName()});
865 modifyDOMError(msg,DOMError.SEVERITY_ERROR, null, elem);
866 boolean continueProcess = fDOMErrorHandler.handleError(fDOMError);
867 // REVISIT: should we terminate upon request?
868 if (!continueProcess) {
869 throw new RuntimeException(
870 DOMMessageFormatter.formatMessage(
871 DOMMessageFormatter.SERIALIZER_DOMAIN,
872 "SerializationStopped", null));
873 }
874 }
875 } else { // uri=null and no colon (DOM L2 node)
876 uri = fNSBinder.getURI(XMLSymbols.EMPTY_STRING);
877
878 if (uri !=null && uri.length() > 0) {
879 // there is a default namespace decl that is bound to
880 // non-zero length uri, output xmlns=""
881 if (fNamespacePrefixes) {
882 printNamespaceAttr(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
883 }
884 fLocalNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
885 fNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
886 }
887 }
888 }
889
890
891 // -----------------------------------------
892 // Fix up namespaces for attributes: per DOM L3
893 // check if prefix/namespace is correct the attributes
894 // -----------------------------------------
895
896 for (i = 0; i < length; i++) {
897
898 attr = (Attr) attrMap.item( i );
899 value = attr.getValue();
900 name = attr.getNodeName();
901
902 uri = attr.getNamespaceURI();
903
904 // Fix attribute that was declared with a prefix and namespace=""
905 if (uri !=null && uri.length() == 0) {
906 uri=null;
907 // we must remove prefix for this attribute
908 name=attr.getLocalName();
909 }
910
911 if (DEBUG) {
912 System.out.println("==>process attribute: "+attr.getNodeName());
913 }
914 // make sure that value is never null.
915 if (value == null) {
916 value=XMLSymbols.EMPTY_STRING;
917 }
918
919 if (uri != null) { // attribute has namespace !=null
920 prefix = attr.getPrefix();
921 prefix = prefix == null ? XMLSymbols.EMPTY_STRING :fSymbolTable.addSymbol(prefix);
922 String localpart = fSymbolTable.addSymbol( attr.getLocalName());
923
924
925
926 // ---------------------------------------------------
927 // print namespace declarations namespace declarations
928 // ---------------------------------------------------
929 if (uri != null && uri.equals(NamespaceContext.XMLNS_URI)) {
930 // check if we need to output this declaration
931 prefix = attr.getPrefix();
932 prefix = (prefix == null ||
933 prefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(prefix);
934 localpart = fSymbolTable.addSymbol( attr.getLocalName());
935 if (prefix == XMLSymbols.PREFIX_XMLNS) { //xmlns:prefix
936 localUri = fLocalNSBinder.getURI(localpart); // local prefix mapping
937 value = fSymbolTable.addSymbol(value);
938 if (value.length() != 0 ) {
939 if (localUri == null) {
940 // declaration was not printed while fixing element namespace binding
941
942 // If the DOM Level 3 namespace-prefixes feature is set to false
943 // do not print xmlns attributes
944 if (fNamespacePrefixes) {
945 printNamespaceAttr(localpart, value);
946 }
947
948 // case 4: <elem xmlns:xx="foo" xx:attr=""/>
949 // where attribute is bound to "bar".
950 // If the xmlns:xx is output here first, later we should not
951 // redeclare "xx" prefix. Instead we would pick up different prefix
952 // for the attribute.
953 // final: <elem xmlns:xx="foo" NS1:attr="" xmlns:NS1="bar"/>
954 fLocalNSBinder.declarePrefix(localpart, value);
955 }
956 } else {
957 // REVISIT: issue error on invalid declarations
958 // xmlns:foo = ""
959 }
960 continue;
961 } else { // xmlns
962 // empty prefix is always bound ("" or some string)
963
964 uri = fNSBinder.getURI(XMLSymbols.EMPTY_STRING);
965 localUri=fLocalNSBinder.getURI(XMLSymbols.EMPTY_STRING);
966 value = fSymbolTable.addSymbol(value);
967 if (localUri == null ){
968 // declaration was not printed while fixing element namespace binding
969 if (fNamespacePrefixes) {
970 printNamespaceAttr(XMLSymbols.EMPTY_STRING, value);
971 }
972 // case 4 does not apply here since attributes can't use
973 // default namespace
974 }
975 continue;
976 }
977
978 }
979 uri = fSymbolTable.addSymbol(uri);
980
981 // find if for this prefix a URI was already declared
982 String declaredURI = fNSBinder.getURI(prefix);
983
984 if (prefix == XMLSymbols.EMPTY_STRING || declaredURI != uri) {
985 // attribute has no prefix (default namespace decl does not apply to attributes)
986 // OR
987 // attribute prefix is not declared
988 // OR
989 // conflict: attr URI does not match the prefix in scope
990
991 name = attr.getNodeName();
992 // Find if any prefix for attributes namespace URI is available
993 // in the scope
994 String declaredPrefix = fNSBinder.getPrefix(uri);
995
996 if (declaredPrefix !=null && declaredPrefix !=XMLSymbols.EMPTY_STRING) {
997 // use the prefix that was found
998 prefix = declaredPrefix;
999 name=prefix+":"+localpart;
1000 } else {
1001 if (DEBUG) {
1002 System.out.println("==> cound not find prefix for the attribute: " +prefix);
1003 }
1004
1005 if (prefix != XMLSymbols.EMPTY_STRING && fLocalNSBinder.getURI(prefix) == null) {
1006 // the current prefix is not null and it has no in scope declaration
1007
1008 // use this prefix
1009 } else {
1010 // find a prefix following the pattern "NS" +index (starting at 1)
1011 // make sure this prefix is not declared in the current scope.
1012 int counter = 1;
1013 prefix = fSymbolTable.addSymbol(PREFIX + counter++);
1014 while (fLocalNSBinder.getURI(prefix)!=null) {
1015 prefix = fSymbolTable.addSymbol(PREFIX +counter++);
1016 }
1017 name=prefix+":"+localpart;
1018 }
1019 // add declaration for the new prefix
1020 if (fNamespacePrefixes) {
1021 printNamespaceAttr(prefix, uri);
1022 }
1023 value = fSymbolTable.addSymbol(value);
1024 fLocalNSBinder.declarePrefix(prefix, value);
1025 fNSBinder.declarePrefix(prefix, uri);
1026 }
1027
1028 // change prefix for this attribute
1029 }
1030
1031 printAttribute (name, (value==null)?XMLSymbols.EMPTY_STRING:value, attr.getSpecified(), attr);
1032 } else { // attribute uri == null
1033 if (attr.getLocalName() == null) {
1034 if (fDOMErrorHandler != null) {
1035 String msg = DOMMessageFormatter.formatMessage(
1036 DOMMessageFormatter.DOM_DOMAIN,
1037 "NullLocalAttrName", new Object[]{attr.getNodeName()});
1038 modifyDOMError(msg, DOMError.SEVERITY_ERROR, null, attr);
1039 boolean continueProcess = fDOMErrorHandler.handleError(fDOMError);
1040 if (!continueProcess) {
1041 // stop the namespace fixup and validation
1042 throw new RuntimeException(
1043 DOMMessageFormatter.formatMessage(
1044 DOMMessageFormatter.SERIALIZER_DOMAIN,
1045 "SerializationStopped", null));
1046 }
1047 }
1048 printAttribute (name, value, attr.getSpecified(), attr);
1049 } else { // uri=null and no colon
1050
1051 // no fix up is needed: default namespace decl does not
1052 // apply to attributes
1053 printAttribute (name, value, attr.getSpecified(), attr);
1054 }
1055 }
1056 } // end loop for attributes
1057
1058 }// end namespace fixup algorithm
1059
1060
1061 // If element has children, then serialize them, otherwise
1062 // serialize en empty tag.
1063 if (elem.hasChildNodes()) {
1064 // Enter an element state, and serialize the children
1065 // one by one. Finally, end the element.
1066 state = enterElementState( null, null, tagName, fPreserveSpace );
1067 state.doCData = _format.isCDataElement( tagName );
1068 state.unescaped = _format.isNonEscapingElement( tagName );
1069 child = elem.getFirstChild();
1070 while (child != null) {
1071 serializeNode( child );
1072 child = child.getNextSibling();
1073 }
1074 if (fNamespaces) {
1075 fNSBinder.popContext();
1076 }
1077 endElementIO( null, null, tagName );
1078 } else {
1079 if (DEBUG) {
1080 System.out.println("==>endElement: " +elem.getNodeName());
1081 }
1082 if (fNamespaces) {
1083 fNSBinder.popContext();
1084 }
1085 _printer.unindent();
1086 _printer.printText( "/>" );
1087 // After element but parent element is no longer empty.
1088 state.afterElement = true;
1089 state.afterComment = false;
1090 state.empty = false;
1091 if (isDocumentState())
1092 _printer.flush();
1093 }
1094 }
1095
1096
1097
1098 /**
1099 * Serializes a namespace attribute with the given prefix and value for URI.
1100 * In case prefix is empty will serialize default namespace declaration.
1101 *
1102 * @param prefix
1103 * @param uri
1104 * @exception IOException
1105 */
1106
1107 private void printNamespaceAttr(String prefix, String uri) throws IOException{
1108 _printer.printSpace();
1109 if (prefix == XMLSymbols.EMPTY_STRING) {
1110 if (DEBUG) {
1111 System.out.println("=>add xmlns=\""+uri+"\" declaration");
1112 }
1113 _printer.printText( XMLSymbols.PREFIX_XMLNS );
1114 } else {
1115 if (DEBUG) {
1116 System.out.println("=>add xmlns:"+prefix+"=\""+uri+"\" declaration");
1117 }
1118 _printer.printText( "xmlns:"+prefix );
1119 }
1120 _printer.printText( "=\"" );
1121 printEscaped( uri );
1122 _printer.printText( '"' );
1123 }
1124
1125
1126
1127 /**
1128 * Prints attribute.
1129 * NOTE: xml:space attribute modifies output format
1130 *
1131 * @param name
1132 * @param value
1133 * @param isSpecified
1134 * @exception IOException
1135 */
1136 private void printAttribute (String name, String value, boolean isSpecified, Attr attr) throws IOException{
1137
1138 if (isSpecified || (features & DOMSerializerImpl.DISCARDDEFAULT) == 0) {
1139 if (fDOMFilter !=null &&
1140 (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ATTRIBUTE)!= 0) {
1141 short code = fDOMFilter.acceptNode(attr);
1142 switch (code) {
1143 case NodeFilter.FILTER_REJECT:
1144 case NodeFilter.FILTER_SKIP: {
1145 return;
1146 }
1147 default: {
1148 // fall through
1149 }
1150 }
1151 }
1152 _printer.printSpace();
1153 _printer.printText( name );
1154 _printer.printText( "=\"" );
1155 printEscaped( value );
1156 _printer.printText( '"' );
1157 }
1158
1159 // If the attribute xml:space exists, determine whether
1160 // to preserve spaces in this and child nodes based on
1161 // its value.
1162 if (name.equals( "xml:space" )) {
1163 if (value.equals( "preserve" ))
1164 fPreserveSpace = true;
1165 else
1166 fPreserveSpace = _format.getPreserveSpace();
1167 }
1168 }
1169
1170 protected String getEntityRef( int ch ) {
1171 // Encode special XML characters into the equivalent character references.
1172 // These five are defined by default for all XML documents.
1173 switch (ch) {
1174 case '<':
1175 return "lt";
1176 case '>':
1177 return "gt";
1178 case '"':
1179 return "quot";
1180 case '\'':
1181 return "apos";
1182 case '&':
1183 return "amp";
1184 }
1185 return null;
1186 }
1187
1188
1189 /** Retrieve and remove the namespaces declarations from the list of attributes.
1190 *
1191 */
1192 private Attributes extractNamespaces( Attributes attrs )
1193 throws SAXException
1194 {
1195 AttributesImpl attrsOnly;
1196 String rawName;
1197 int i;
1198 int indexColon;
1199 String prefix;
1200 int length;
1201
1202 if (attrs == null) {
1203 return null;
1204 }
1205 length = attrs.getLength();
1206 attrsOnly = new AttributesImpl( attrs );
1207
1208 for (i = length - 1 ; i >= 0 ; --i) {
1209 rawName = attrsOnly.getQName( i );
1210
1211 //We have to exclude the namespaces declarations from the attributes
1212 //Append only when the feature http://xml.org/sax/features/namespace-prefixes"
1213 //is TRUE
1214 if (rawName.startsWith( "xmlns" )) {
1215 if (rawName.length() == 5) {
1216 startPrefixMapping( "", attrs.getValue( i ) );
1217 attrsOnly.removeAttribute( i );
1218 } else if (rawName.charAt(5) == ':') {
1219 startPrefixMapping(rawName.substring(6), attrs.getValue(i));
1220 attrsOnly.removeAttribute( i );
1221 }
1222 }
1223 }
1224 return attrsOnly;
1225 }
1226
1227 //
1228 // Printing attribute value
1229 //
1230 protected void printEscaped(String source) throws IOException {
1231 int length = source.length();
1232 for (int i = 0; i < length; ++i) {
1233 int ch = source.charAt(i);
1234 if (!XMLChar.isValid(ch)) {
1235 if (++i < length) {
1236 surrogates(ch, source.charAt(i));
1237 } else {
1238 fatalError("The character '" + (char) ch + "' is an invalid XML character");
1239 }
1240 continue;
1241 }
1242 // escape NL, CR, TAB
1243 if (ch == '\n' || ch == '\r' || ch == '\t') {
1244 printHex(ch);
1245 } else if (ch == '<') {
1246 _printer.printText("<");
1247 } else if (ch == '&') {
1248 _printer.printText("&");
1249 } else if (ch == '"') {
1250 _printer.printText(""");
1251 } else if ((ch >= ' ' && _encodingInfo.isPrintable((char) ch))) {
1252 _printer.printText((char) ch);
1253 } else {
1254 printHex(ch);
1255 }
1256 }
1257 }
1258
1259 /** print text data */
1260 protected void printXMLChar( int ch) throws IOException {
1261 if (ch == '\r') {
1262 printHex(ch);
1263 } else if ( ch == '<') {
1264 _printer.printText("<");
1265 } else if (ch == '&') {
1266 _printer.printText("&");
1267 } else if (ch == '>'){
1268 // character sequence "]]>" can't appear in content, therefore
1269 // we should escape '>'
1270 _printer.printText(">");
1271 } else if ( ch == '\n' || ch == '\t' ||
1272 ( ch >= ' ' && _encodingInfo.isPrintable((char)ch))) {
1273 _printer.printText((char)ch);
1274 } else {
1275 printHex(ch);
1276 }
1277 }
1278
1279 protected void printText( String text, boolean preserveSpace, boolean unescaped )
1280 throws IOException {
1281 int index;
1282 char ch;
1283 int length = text.length();
1284 if ( preserveSpace ) {
1285 // Preserving spaces: the text must print exactly as it is,
1286 // without breaking when spaces appear in the text and without
1287 // consolidating spaces. If a line terminator is used, a line
1288 // break will occur.
1289 for ( index = 0 ; index < length ; ++index ) {
1290 ch = text.charAt( index );
1291 if (!XMLChar.isValid(ch)) {
1292 // check if it is surrogate
1293 if (++index <length) {
1294 surrogates(ch, text.charAt(index));
1295 } else {
1296 fatalError("The character '"+(char)ch+"' is an invalid XML character");
1297 }
1298 continue;
1299 }
1300 if ( unescaped ) {
1301 _printer.printText( ch );
1302 } else
1303 printXMLChar( ch );
1304 }
1305 } else {
1306 // Not preserving spaces: print one part at a time, and
1307 // use spaces between parts to break them into different
1308 // lines. Spaces at beginning of line will be stripped
1309 // by printing mechanism. Line terminator is treated
1310 // no different than other text part.
1311 for ( index = 0 ; index < length ; ++index ) {
1312 ch = text.charAt( index );
1313 if (!XMLChar.isValid(ch)) {
1314 // check if it is surrogate
1315 if (++index <length) {
1316 surrogates(ch, text.charAt(index));
1317 } else {
1318 fatalError("The character '"+(char)ch+"' is an invalid XML character");
1319 }
1320 continue;
1321 }
1322
1323 if ( unescaped )
1324 _printer.printText( ch );
1325 else
1326 printXMLChar( ch);
1327 }
1328 }
1329 }
1330
1331
1332
1333 protected void printText( char[] chars, int start, int length,
1334 boolean preserveSpace, boolean unescaped ) throws IOException {
1335 int index;
1336 char ch;
1337
1338 if ( preserveSpace ) {
1339 // Preserving spaces: the text must print exactly as it is,
1340 // without breaking when spaces appear in the text and without
1341 // consolidating spaces. If a line terminator is used, a line
1342 // break will occur.
1343 while ( length-- > 0 ) {
1344 ch = chars[start++];
1345 if (!XMLChar.isValid(ch)) {
1346 // check if it is surrogate
1347 if ( length-- > 0 ) {
1348 surrogates(ch, chars[start++]);
1349 } else {
1350 fatalError("The character '"+(char)ch+"' is an invalid XML character");
1351 }
1352 continue;
1353 }
1354 if ( unescaped )
1355 _printer.printText( ch );
1356 else
1357 printXMLChar( ch );
1358 }
1359 } else {
1360 // Not preserving spaces: print one part at a time, and
1361 // use spaces between parts to break them into different
1362 // lines. Spaces at beginning of line will be stripped
1363 // by printing mechanism. Line terminator is treated
1364 // no different than other text part.
1365 while ( length-- > 0 ) {
1366 ch = chars[start++];
1367 if (!XMLChar.isValid(ch)) {
1368 // check if it is surrogate
1369 if ( length-- > 0 ) {
1370 surrogates(ch, chars[start++]);
1371 } else {
1372 fatalError("The character '"+(char)ch+"' is an invalid XML character");
1373 }
1374 continue;
1375 }
1376 if ( unescaped )
1377 _printer.printText( ch );
1378 else
1379 printXMLChar( ch );
1380 }
1381 }
1382 }
1383
1384
1385 /**
1386 * DOM Level 3:
1387 * Check a node to determine if it contains unbound namespace prefixes.
1388 *
1389 * @param node The node to check for unbound namespace prefices
1390 */
1391 protected void checkUnboundNamespacePrefixedNode (Node node) throws IOException{
1392
1393 if (fNamespaces) {
1394
1395 if (DEBUG) {
1396 System.out.println("==>serializeNode("+node.getNodeName()+") [Entity Reference - Namespaces on]");
1397 System.out.println("==>Declared Prefix Count: " + fNSBinder.getDeclaredPrefixCount());
1398 System.out.println("==>Node Name: " + node.getNodeName());
1399 System.out.println("==>First Child Node Name: " + node.getFirstChild().getNodeName());
1400 System.out.println("==>First Child Node Prefix: " + node.getFirstChild().getPrefix());
1401 System.out.println("==>First Child Node NamespaceURI: " + node.getFirstChild().getNamespaceURI());
1402 }
1403
1404
1405 Node child, next;
1406 for (child = node.getFirstChild(); child != null; child = next) {
1407 next = child.getNextSibling();
1408 if (DEBUG) {
1409 System.out.println("==>serializeNode("+child.getNodeName()+") [Child Node]");
1410 System.out.println("==>serializeNode("+child.getPrefix()+") [Child Node Prefix]");
1411 }
1412
1413 //If a NamespaceURI is not declared for the current
1414 //node's prefix, raise a fatal error.
1415 String prefix = child.getPrefix();
1416 prefix = (prefix == null ||
1417 prefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(prefix);
1418 if (fNSBinder.getURI(prefix) == null && prefix != null) {
1419 fatalError("The replacement text of the entity node '"
1420 + node.getNodeName()
1421 + "' contains an element node '"
1422 + child.getNodeName()
1423 + "' with an undeclared prefix '"
1424 + prefix + "'.");
1425 }
1426
1427 if (child.getNodeType() == Node.ELEMENT_NODE) {
1428
1429 NamedNodeMap attrs = child.getAttributes();
1430
1431 for (int i = 0; i< attrs.getLength(); i++ ) {
1432
1433 String attrPrefix = attrs.item(i).getPrefix();
1434 attrPrefix = (attrPrefix == null ||
1435 attrPrefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(attrPrefix);
1436 if (fNSBinder.getURI(attrPrefix) == null && attrPrefix != null) {
1437 fatalError("The replacement text of the entity node '"
1438 + node.getNodeName()
1439 + "' contains an element node '"
1440 + child.getNodeName()
1441 + "' with an attribute '"
1442 + attrs.item(i).getNodeName()
1443 + "' an undeclared prefix '"
1444 + attrPrefix + "'.");
1445 }
1446
1447 }
1448
1449 }
1450
1451 if (child.hasChildNodes()) {
1452 checkUnboundNamespacePrefixedNode(child);
1453 }
1454 }
1455 }
1456 }
1457
1458 public boolean reset() {
1459 super.reset();
1460 if (fNSBinder != null){
1461 fNSBinder.reset();
1462 // during serialization always have a mapping to empty string
1463 // so we assume there is a declaration.
1464 fNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
1465 }
1466 return true;
1467 }
1468
1469 }