1 /*
2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
3 * @LastModified: Oct 2017
4 */
5 /*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements. See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22 package com.sun.org.apache.xml.internal.serializer.dom3;
23
24 import com.sun.org.apache.xerces.internal.util.XML11Char;
25 import com.sun.org.apache.xerces.internal.util.XMLChar;
26 import com.sun.org.apache.xml.internal.serializer.OutputPropertiesFactory;
27 import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
28 import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
29 import com.sun.org.apache.xml.internal.serializer.utils.Utils;
30 import java.io.IOException;
31 import java.io.Writer;
32 import java.util.Collections;
33 import java.util.Enumeration;
34 import java.util.HashMap;
35 import java.util.Map;
36 import java.util.Properties;
37 import org.w3c.dom.Attr;
38 import org.w3c.dom.CDATASection;
39 import org.w3c.dom.Comment;
40 import org.w3c.dom.DOMError;
41 import org.w3c.dom.DOMErrorHandler;
42 import org.w3c.dom.Document;
43 import org.w3c.dom.DocumentType;
44 import org.w3c.dom.Element;
45 import org.w3c.dom.Entity;
46 import org.w3c.dom.EntityReference;
47 import org.w3c.dom.NamedNodeMap;
48 import org.w3c.dom.Node;
49 import org.w3c.dom.NodeList;
50 import org.w3c.dom.ProcessingInstruction;
51 import org.w3c.dom.Text;
52 import org.w3c.dom.ls.LSSerializerFilter;
53 import org.w3c.dom.traversal.NodeFilter;
54 import org.xml.sax.Locator;
55 import org.xml.sax.SAXException;
56 import org.xml.sax.ext.LexicalHandler;
57 import org.xml.sax.helpers.LocatorImpl;
58
59 /**
60 * Built on org.apache.xml.serializer.TreeWalker and adds functionality to
61 * traverse and serialize a DOM Node (Level 2 or Level 3) as specified in
62 * the DOM Level 3 LS Recommedation by evaluating and applying DOMConfiguration
63 * parameters and filters if any during serialization.
64 *
65 * @xsl.usage internal
66 */
67 final class DOM3TreeWalker {
68
69 /**
70 * The SerializationHandler, it extends ContentHandler and when
71 * this class is instantiated via the constructor provided, a
72 * SerializationHandler object is passed to it.
73 */
74 private SerializationHandler fSerializer = null;
75
76 /** We do not need DOM2Helper since DOM Level 3 LS applies to DOM Level 2 or newer */
77
78 /** Locator object for this TreeWalker */
79 private LocatorImpl fLocator = new LocatorImpl();
80
81 /** ErrorHandler */
82 private DOMErrorHandler fErrorHandler = null;
83
84 /** LSSerializerFilter */
85 private LSSerializerFilter fFilter = null;
86
87 /** If the serializer is an instance of a LexicalHandler */
88 private LexicalHandler fLexicalHandler = null;
89
90 private int fWhatToShowFilter;
91
92 /** New Line character to use in serialization */
93 private String fNewLine = null;
94
95 /** DOMConfiguration Properties */
96 private Properties fDOMConfigProperties = null;
97
98 /** Keeps track if we are in an entity reference when entities=true */
99 private boolean fInEntityRef = false;
100
101 /** Stores the version of the XML document to be serialize */
102 private String fXMLVersion = null;
103
104 /** XML Version, default 1.0 */
105 private boolean fIsXMLVersion11 = false;
106
107 /** Is the Node a Level 3 DOM node */
108 private boolean fIsLevel3DOM = false;
109
110 /** DOM Configuration Parameters */
111 private int fFeatures = 0;
112
113 /** Flag indicating whether following text to be processed is raw text */
114 boolean fNextIsRaw = false;
115
116 //
117 private static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
118
119 //
120 private static final String XMLNS_PREFIX = "xmlns";
121
122 //
123 private static final String XML_URI = "http://www.w3.org/XML/1998/namespace";
124
125 //
126 private static final String XML_PREFIX = "xml";
127
128 /** stores namespaces in scope */
129 protected NamespaceSupport fNSBinder;
130
131 /** stores all namespace bindings on the current element */
132 protected NamespaceSupport fLocalNSBinder;
133
134 /** stores the current element depth */
135 private int fElementDepth = 0;
136
137 // ***********************************************************************
138 // DOMConfiguration paramter settings
139 // ***********************************************************************
140 // Parameter canonical-form, true [optional] - NOT SUPPORTED
141 private final static int CANONICAL = 0x1 << 0;
142
143 // Parameter cdata-sections, true [required] (default)
144 private final static int CDATA = 0x1 << 1;
145
146 // Parameter check-character-normalization, true [optional] - NOT SUPPORTED
147 private final static int CHARNORMALIZE = 0x1 << 2;
148
149 // Parameter comments, true [required] (default)
150 private final static int COMMENTS = 0x1 << 3;
151
152 // Parameter datatype-normalization, true [optional] - NOT SUPPORTED
153 private final static int DTNORMALIZE = 0x1 << 4;
154
155 // Parameter element-content-whitespace, true [required] (default) - value - false [optional] NOT SUPPORTED
156 private final static int ELEM_CONTENT_WHITESPACE = 0x1 << 5;
157
158 // Parameter entities, true [required] (default)
159 private final static int ENTITIES = 0x1 << 6;
160
161 // Parameter infoset, true [required] (default), false has no effect --> True has no effect for the serializer
162 private final static int INFOSET = 0x1 << 7;
163
164 // Parameter namespaces, true [required] (default)
165 private final static int NAMESPACES = 0x1 << 8;
166
167 // Parameter namespace-declarations, true [required] (default)
168 private final static int NAMESPACEDECLS = 0x1 << 9;
169
170 // Parameter normalize-characters, true [optional] - NOT SUPPORTED
171 private final static int NORMALIZECHARS = 0x1 << 10;
172
173 // Parameter split-cdata-sections, true [required] (default)
174 private final static int SPLITCDATA = 0x1 << 11;
175
176 // Parameter validate, true [optional] - NOT SUPPORTED
177 private final static int VALIDATE = 0x1 << 12;
178
179 // Parameter validate-if-schema, true [optional] - NOT SUPPORTED
180 private final static int SCHEMAVALIDATE = 0x1 << 13;
181
182 // Parameter split-cdata-sections, true [required] (default)
183 private final static int WELLFORMED = 0x1 << 14;
184
185 // Parameter discard-default-content, true [required] (default)
186 // Not sure how this will be used in level 2 Documents
187 private final static int DISCARDDEFAULT = 0x1 << 15;
188
189 // Parameter format-pretty-print, true [optional]
190 private final static int PRETTY_PRINT = 0x1 << 16;
191
192 // Parameter ignore-unknown-character-denormalizations, true [required] (default)
193 // We currently do not support XML 1.1 character normalization
194 private final static int IGNORE_CHAR_DENORMALIZE = 0x1 << 17;
195
196 // Parameter discard-default-content, true [required] (default)
197 private final static int XMLDECL = 0x1 << 18;
198
199 /**
200 * Constructor.
201 * @param contentHandler serialHandler The implemention of the SerializationHandler interface
202 */
203 DOM3TreeWalker(
204 SerializationHandler serialHandler,
205 DOMErrorHandler errHandler,
206 LSSerializerFilter filter,
207 String newLine) {
208 fSerializer = serialHandler;
209 //fErrorHandler = errHandler == null ? new DOMErrorHandlerImpl() : errHandler; // Should we be using the default?
210 fErrorHandler = errHandler;
211 fFilter = filter;
212 fLexicalHandler = null;
213 fNewLine = newLine;
214
215 fNSBinder = new NamespaceSupport();
216 fLocalNSBinder = new NamespaceSupport();
217
218 fDOMConfigProperties = fSerializer.getOutputFormat();
219 fSerializer.setDocumentLocator(fLocator);
220 initProperties(fDOMConfigProperties);
221 }
222
223 /**
224 * Perform a pre-order traversal non-recursive style.
225 *
226 * Note that TreeWalker assumes that the subtree is intended to represent
227 * a complete (though not necessarily well-formed) document and, during a
228 * traversal, startDocument and endDocument will always be issued to the
229 * SAX listener.
230 *
231 * @param pos Node in the tree where to start traversal
232 *
233 * @throws TransformerException
234 */
235 public void traverse(Node pos) throws org.xml.sax.SAXException {
236 this.fSerializer.startDocument();
237
238 // Determine if the Node is a DOM Level 3 Core Node.
239 if (pos.getNodeType() != Node.DOCUMENT_NODE) {
240 Document ownerDoc = pos.getOwnerDocument();
241 if (ownerDoc != null
242 && ownerDoc.getImplementation().hasFeature("Core", "3.0")) {
243 fIsLevel3DOM = true;
244 }
245 } else {
246 if (((Document) pos)
247 .getImplementation()
248 .hasFeature("Core", "3.0")) {
249 fIsLevel3DOM = true;
250 }
251 }
252
253 if (fSerializer instanceof LexicalHandler) {
254 fLexicalHandler = ((LexicalHandler) this.fSerializer);
255 }
256
257 if (fFilter != null)
258 fWhatToShowFilter = fFilter.getWhatToShow();
259
260 Node top = pos;
261
262 while (null != pos) {
263 startNode(pos);
264
265 Node nextNode = null;
266
267 nextNode = pos.getFirstChild();
268
269 while (null == nextNode) {
270 endNode(pos);
271
272 if (top.equals(pos))
273 break;
274
275 nextNode = pos.getNextSibling();
276
277 if (null == nextNode) {
278 pos = pos.getParentNode();
279
280 if ((null == pos) || (top.equals(pos))) {
281 if (null != pos)
282 endNode(pos);
283
284 nextNode = null;
285
286 break;
287 }
288 }
289 }
290
291 pos = nextNode;
292 }
293 this.fSerializer.endDocument();
294 }
295
296 /**
297 * Perform a pre-order traversal non-recursive style.
298
299 * Note that TreeWalker assumes that the subtree is intended to represent
300 * a complete (though not necessarily well-formed) document and, during a
301 * traversal, startDocument and endDocument will always be issued to the
302 * SAX listener.
303 *
304 * @param pos Node in the tree where to start traversal
305 * @param top Node in the tree where to end traversal
306 *
307 * @throws TransformerException
308 */
309 public void traverse(Node pos, Node top) throws org.xml.sax.SAXException {
310
311 this.fSerializer.startDocument();
312
313 // Determine if the Node is a DOM Level 3 Core Node.
314 if (pos.getNodeType() != Node.DOCUMENT_NODE) {
315 Document ownerDoc = pos.getOwnerDocument();
316 if (ownerDoc != null
317 && ownerDoc.getImplementation().hasFeature("Core", "3.0")) {
318 fIsLevel3DOM = true;
319 }
320 } else {
321 if (((Document) pos)
322 .getImplementation()
323 .hasFeature("Core", "3.0")) {
324 fIsLevel3DOM = true;
325 }
326 }
327
328 if (fSerializer instanceof LexicalHandler) {
329 fLexicalHandler = ((LexicalHandler) this.fSerializer);
330 }
331
332 if (fFilter != null)
333 fWhatToShowFilter = fFilter.getWhatToShow();
334
335 while (null != pos) {
336 startNode(pos);
337
338 Node nextNode = null;
339
340 nextNode = pos.getFirstChild();
341
342 while (null == nextNode) {
343 endNode(pos);
344
345 if ((null != top) && top.equals(pos))
346 break;
347
348 nextNode = pos.getNextSibling();
349
350 if (null == nextNode) {
351 pos = pos.getParentNode();
352
353 if ((null == pos) || ((null != top) && top.equals(pos))) {
354 nextNode = null;
355
356 break;
357 }
358 }
359 }
360
361 pos = nextNode;
362 }
363 this.fSerializer.endDocument();
364 }
365
366 /**
367 * Optimized dispatch of characters.
368 */
369 private final void dispatachChars(Node node)
370 throws org.xml.sax.SAXException {
371 if (fSerializer != null) {
372 String data = ((Text) node).getData();
373 this.fSerializer.characters(data.toCharArray(), 0, data.length());
374 }
375 }
376
377 /**
378 * Start processing given node
379 *
380 * @param node Node to process
381 *
382 * @throws org.xml.sax.SAXException
383 */
384 protected void startNode(Node node) throws org.xml.sax.SAXException {
385 if (node instanceof Locator) {
386 Locator loc = (Locator) node;
387 fLocator.setColumnNumber(loc.getColumnNumber());
388 fLocator.setLineNumber(loc.getLineNumber());
389 fLocator.setPublicId(loc.getPublicId());
390 fLocator.setSystemId(loc.getSystemId());
391 } else {
392 fLocator.setColumnNumber(0);
393 fLocator.setLineNumber(0);
394 }
395
396 switch (node.getNodeType()) {
397 case Node.DOCUMENT_TYPE_NODE :
398 serializeDocType((DocumentType) node, true);
399 break;
400 case Node.COMMENT_NODE :
401 serializeComment((Comment) node);
402 break;
403 case Node.DOCUMENT_FRAGMENT_NODE :
404 // Children are traversed
405 break;
406 case Node.DOCUMENT_NODE :
407 break;
408 case Node.ELEMENT_NODE :
409 serializeElement((Element) node, true);
410 break;
411 case Node.PROCESSING_INSTRUCTION_NODE :
412 serializePI((ProcessingInstruction) node);
413 break;
414 case Node.CDATA_SECTION_NODE :
415 serializeCDATASection((CDATASection) node);
416 break;
417 case Node.TEXT_NODE :
418 serializeText((Text) node);
419 break;
420 case Node.ENTITY_REFERENCE_NODE :
421 serializeEntityReference((EntityReference) node, true);
422 break;
423 default :
424 }
425 }
426
427 /**
428 * End processing of given node
429 *
430 *
431 * @param node Node we just finished processing
432 *
433 * @throws org.xml.sax.SAXException
434 */
435 protected void endNode(Node node) throws org.xml.sax.SAXException {
436
437 switch (node.getNodeType()) {
438 case Node.DOCUMENT_NODE :
439 break;
440 case Node.DOCUMENT_TYPE_NODE :
441 serializeDocType((DocumentType) node, false);
442 break;
443 case Node.ELEMENT_NODE :
444 serializeElement((Element) node, false);
445 break;
446 case Node.CDATA_SECTION_NODE :
447 break;
448 case Node.ENTITY_REFERENCE_NODE :
449 serializeEntityReference((EntityReference) node, false);
450 break;
451 default :
452 }
453 }
454
455 // ***********************************************************************
456 // Node serialization methods
457 // ***********************************************************************
458 /**
459 * Applies a filter on the node to serialize
460 *
461 * @param node The Node to serialize
462 * @return True if the node is to be serialized else false if the node
463 * is to be rejected or skipped.
464 */
465 protected boolean applyFilter(Node node, int nodeType) {
466 if (fFilter != null && (fWhatToShowFilter & nodeType) != 0) {
467
468 short code = fFilter.acceptNode(node);
469 switch (code) {
470 case NodeFilter.FILTER_REJECT :
471 case NodeFilter.FILTER_SKIP :
472 return false; // skip the node
473 default : // fall through..
474 }
475 }
476 return true;
477 }
478
479 /**
480 * Serializes a Document Type Node.
481 *
482 * @param node The Docuemnt Type Node to serialize
483 * @param bStart Invoked at the start or end of node. Default true.
484 */
485 protected void serializeDocType(DocumentType node, boolean bStart)
486 throws SAXException {
487 // The DocType and internalSubset can not be modified in DOM and is
488 // considered to be well-formed as the outcome of successful parsing.
489 String docTypeName = node.getNodeName();
490 String publicId = node.getPublicId();
491 String systemId = node.getSystemId();
492 String internalSubset = node.getInternalSubset();
493
494 //DocumentType nodes are never passed to the filter
495
496 if (internalSubset != null && !"".equals(internalSubset)) {
497
498 if (bStart) {
499 try {
500 // The Serializer does not provide a way to write out the
501 // DOCTYPE internal subset via an event call, so we write it
502 // out here.
503 Writer writer = fSerializer.getWriter();
504 StringBuffer dtd = new StringBuffer();
505
506 dtd.append("<!DOCTYPE ");
507 dtd.append(docTypeName);
508 if (null != publicId) {
509 dtd.append(" PUBLIC \"");
510 dtd.append(publicId);
511 dtd.append('\"');
512 }
513
514 if (null != systemId) {
515 if (null == publicId) {
516 dtd.append(" SYSTEM \"");
517 } else {
518 dtd.append(" \"");
519 }
520 dtd.append(systemId);
521 dtd.append('\"');
522 }
523
524 dtd.append(" [ ");
525
526 dtd.append(fNewLine);
527 dtd.append(internalSubset);
528 dtd.append("]>");
529 dtd.append(fNewLine);
530
531 writer.write(dtd.toString());
532 writer.flush();
533
534 } catch (IOException e) {
535 throw new SAXException(Utils.messages.createMessage(
536 MsgKey.ER_WRITING_INTERNAL_SUBSET, null), e);
537 }
538 } // else if !bStart do nothing
539
540 } else {
541
542 if (bStart) {
543 if (fLexicalHandler != null) {
544 fLexicalHandler.startDTD(docTypeName, publicId, systemId);
545 }
546 } else {
547 if (fLexicalHandler != null) {
548 fLexicalHandler.endDTD();
549 }
550 }
551 }
552 }
553
554 /**
555 * Serializes a Comment Node.
556 *
557 * @param node The Comment Node to serialize
558 */
559 protected void serializeComment(Comment node) throws SAXException {
560 // comments=true
561 if ((fFeatures & COMMENTS) != 0) {
562 String data = node.getData();
563
564 // well-formed=true
565 if ((fFeatures & WELLFORMED) != 0) {
566 isCommentWellFormed(data);
567 }
568
569 if (fLexicalHandler != null) {
570 // apply the LSSerializer filter after the operations requested by the
571 // DOMConfiguration parameters have been applied
572 if (!applyFilter(node, NodeFilter.SHOW_COMMENT)) {
573 return;
574 }
575
576 fLexicalHandler.comment(data.toCharArray(), 0, data.length());
577 }
578 }
579 }
580
581 /**
582 * Serializes an Element Node.
583 *
584 * @param node The Element Node to serialize
585 * @param bStart Invoked at the start or end of node.
586 */
587 protected void serializeElement(Element node, boolean bStart)
588 throws SAXException {
589 if (bStart) {
590 fElementDepth++;
591
592 // We use the Xalan specific startElement and starPrefixMapping calls
593 // (and addAttribute and namespaceAfterStartElement) as opposed to
594 // SAX specific, for performance reasons as they reduce the overhead
595 // of creating an AttList object upfront.
596
597 // well-formed=true
598 if ((fFeatures & WELLFORMED) != 0) {
599 isElementWellFormed(node);
600 }
601
602 // REVISIT: We apply the LSSerializer filter for elements before
603 // namesapce fixup
604 if (!applyFilter(node, NodeFilter.SHOW_ELEMENT)) {
605 return;
606 }
607
608 // namespaces=true, record and fixup namspaced element
609 if ((fFeatures & NAMESPACES) != 0) {
610 fNSBinder.pushContext();
611 fLocalNSBinder.reset();
612
613 recordLocalNSDecl(node);
614 fixupElementNS(node);
615 }
616
617 // Namespace normalization
618 fSerializer.startElement(
619 node.getNamespaceURI(),
620 node.getLocalName(),
621 node.getNodeName());
622
623 serializeAttList(node);
624
625 } else {
626 fElementDepth--;
627
628 // apply the LSSerializer filter
629 if (!applyFilter(node, NodeFilter.SHOW_ELEMENT)) {
630 return;
631 }
632
633 this.fSerializer.endElement(
634 node.getNamespaceURI(),
635 node.getLocalName(),
636 node.getNodeName());
637 // since endPrefixMapping was not used by SerializationHandler it was removed
638 // for performance reasons.
639
640 if ((fFeatures & NAMESPACES) != 0 ) {
641 fNSBinder.popContext();
642 }
643
644 }
645 }
646
647 /**
648 * Serializes the Attr Nodes of an Element.
649 *
650 * @param node The OwnerElement whose Attr Nodes are to be serialized.
651 */
652 protected void serializeAttList(Element node) throws SAXException {
653 NamedNodeMap atts = node.getAttributes();
654 int nAttrs = atts.getLength();
655
656 for (int i = 0; i < nAttrs; i++) {
657 Node attr = atts.item(i);
658
659 String localName = attr.getLocalName();
660 String attrName = attr.getNodeName();
661 String attrPrefix = attr.getPrefix() == null ? "" : attr.getPrefix();
662 String attrValue = attr.getNodeValue();
663
664 // Determine the Attr's type.
665 String type = null;
666 if (fIsLevel3DOM) {
667 type = ((Attr) attr).getSchemaTypeInfo().getTypeName();
668 }
669 type = type == null ? "CDATA" : type;
670
671 String attrNS = attr.getNamespaceURI();
672 if (attrNS !=null && attrNS.length() == 0) {
673 attrNS=null;
674 // we must remove prefix for this attribute
675 attrName=attr.getLocalName();
676 }
677
678 boolean isSpecified = ((Attr) attr).getSpecified();
679 boolean addAttr = true;
680 boolean applyFilter = false;
681 boolean xmlnsAttr =
682 attrName.equals("xmlns") || attrName.startsWith("xmlns:");
683
684 // well-formed=true
685 if ((fFeatures & WELLFORMED) != 0) {
686 isAttributeWellFormed(attr);
687 }
688
689 //-----------------------------------------------------------------
690 // start Attribute namespace fixup
691 //-----------------------------------------------------------------
692 // namespaces=true, normalize all non-namespace attributes
693 // Step 3. Attribute
694 if ((fFeatures & NAMESPACES) != 0 && !xmlnsAttr) {
695
696 // If the Attr has a namespace URI
697 if (attrNS != null) {
698 attrPrefix = attrPrefix == null ? "" : attrPrefix;
699
700 String declAttrPrefix = fNSBinder.getPrefix(attrNS);
701 String declAttrNS = fNSBinder.getURI(attrPrefix);
702
703 // attribute has no prefix (default namespace decl does not apply to
704 // attributes)
705 // OR
706 // attribute prefix is not declared
707 // OR
708 // conflict: attribute has a prefix that conflicts with a binding
709 if ("".equals(attrPrefix) || "".equals(declAttrPrefix)
710 || !attrPrefix.equals(declAttrPrefix)) {
711
712 // namespaceURI matches an in scope declaration of one or
713 // more prefixes
714 if (declAttrPrefix != null && !"".equals(declAttrPrefix)) {
715 // pick the prefix that was found and change attribute's
716 // prefix and nodeName.
717 attrPrefix = declAttrPrefix;
718
719 if (declAttrPrefix.length() > 0 ) {
720 attrName = declAttrPrefix + ":" + localName;
721 } else {
722 attrName = localName;
723 }
724 } else {
725 // The current prefix is not null and it has no in scope
726 // declaration
727 if (attrPrefix != null && !"".equals(attrPrefix)
728 && declAttrNS == null) {
729 // declare this prefix
730 if ((fFeatures & NAMESPACEDECLS) != 0) {
731 fSerializer.addAttribute(XMLNS_URI, attrPrefix,
732 XMLNS_PREFIX + ":" + attrPrefix, "CDATA",
733 attrNS);
734 fNSBinder.declarePrefix(attrPrefix, attrNS);
735 fLocalNSBinder.declarePrefix(attrPrefix, attrNS);
736 }
737 } else {
738 // find a prefix following the pattern "NS" +index
739 // (starting at 1)
740 // make sure this prefix is not declared in the current
741 // scope.
742 int counter = 1;
743 attrPrefix = "NS" + counter++;
744
745 while (fLocalNSBinder.getURI(attrPrefix) != null) {
746 attrPrefix = "NS" + counter++;
747 }
748 // change attribute's prefix and Name
749 attrName = attrPrefix + ":" + localName;
750
751 // create a local namespace declaration attribute
752 // Add the xmlns declaration attribute
753 if ((fFeatures & NAMESPACEDECLS) != 0) {
754
755 fSerializer.addAttribute(XMLNS_URI, attrPrefix,
756 XMLNS_PREFIX + ":" + attrPrefix, "CDATA",
757 attrNS);
758 fNSBinder.declarePrefix(attrPrefix, attrNS);
759 fLocalNSBinder.declarePrefix(attrPrefix, attrNS);
760 }
761 }
762 }
763 }
764
765 } else { // if the Attr has no namespace URI
766 // Attr has no localName
767 if (localName == null) {
768 // DOM Level 1 node!
769 String msg = Utils.messages.createMessage(
770 MsgKey.ER_NULL_LOCAL_ELEMENT_NAME,
771 new Object[] { attrName });
772
773 if (fErrorHandler != null) {
774 fErrorHandler
775 .handleError(new DOMErrorImpl(
776 DOMError.SEVERITY_ERROR, msg,
777 MsgKey.ER_NULL_LOCAL_ELEMENT_NAME, null,
778 null, null));
779 }
780
781 } else { // uri=null and no colon
782 // attr has no namespace URI and no prefix
783 // no action is required, since attrs don't use default
784 }
785 }
786
787 }
788
789
790 // discard-default-content=true
791 // Default attr's are not passed to the filter and this contraint
792 // is applied only when discard-default-content=true
793 // What about default xmlns attributes???? check for xmlnsAttr
794 if ((((fFeatures & DISCARDDEFAULT) != 0) && isSpecified)
795 || ((fFeatures & DISCARDDEFAULT) == 0)) {
796 applyFilter = true;
797 } else {
798 addAttr = false;
799 }
800
801 if (applyFilter) {
802 // apply the filter for Attributes that are not default attributes
803 // or namespace decl attributes
804 if (fFilter != null
805 && (fFilter.getWhatToShow() & NodeFilter.SHOW_ATTRIBUTE)
806 != 0) {
807
808 if (!xmlnsAttr) {
809 short code = fFilter.acceptNode(attr);
810 switch (code) {
811 case NodeFilter.FILTER_REJECT :
812 case NodeFilter.FILTER_SKIP :
813 addAttr = false;
814 break;
815 default : //fall through..
816 }
817 }
818 }
819 }
820
821 // if the node is a namespace node
822 if (addAttr && xmlnsAttr) {
823 // If namespace-declarations=true, add the node , else don't add it
824 if ((fFeatures & NAMESPACEDECLS) != 0) {
825 // The namespace may have been fixed up, in that case don't add it.
826 if (localName != null && !"".equals(localName)) {
827 fSerializer.addAttribute(attrNS, localName, attrName, type, attrValue);
828 }
829 }
830 } else if (
831 addAttr && !xmlnsAttr) { // if the node is not a namespace node
832 // If namespace-declarations=true, add the node with the Attr nodes namespaceURI
833 // else add the node setting it's namespace to null or else the serializer will later
834 // attempt to add a xmlns attr for the prefixed attribute
835 if (((fFeatures & NAMESPACEDECLS) != 0) && (attrNS != null)) {
836 fSerializer.addAttribute(
837 attrNS,
838 localName,
839 attrName,
840 type,
841 attrValue);
842 } else {
843 fSerializer.addAttribute(
844 "",
845 localName,
846 attrName,
847 type,
848 attrValue);
849 }
850 }
851
852 //
853 if (xmlnsAttr && ((fFeatures & NAMESPACEDECLS) != 0)) {
854 int index;
855 // Use "" instead of null, as Xerces likes "" for the
856 // name of the default namespace. Fix attributed
857 // to "Steven Murray" <smurray@ebt.com>.
858 String prefix =
859 (index = attrName.indexOf(":")) < 0
860 ? ""
861 : attrName.substring(index + 1);
862
863 if (!"".equals(prefix)) {
864 fSerializer.namespaceAfterStartElement(prefix, attrValue);
865 }
866 }
867 }
868
869 }
870
871 /**
872 * Serializes an ProcessingInstruction Node.
873 *
874 * @param node The ProcessingInstruction Node to serialize
875 */
876 protected void serializePI(ProcessingInstruction node)
877 throws SAXException {
878 ProcessingInstruction pi = node;
879 String name = pi.getNodeName();
880
881 // well-formed=true
882 if ((fFeatures & WELLFORMED) != 0) {
883 isPIWellFormed(node);
884 }
885
886 // apply the LSSerializer filter
887 if (!applyFilter(node, NodeFilter.SHOW_PROCESSING_INSTRUCTION)) {
888 return;
889 }
890
891 // String data = pi.getData();
892 if (name.equals("xslt-next-is-raw")) {
893 fNextIsRaw = true;
894 } else {
895 this.fSerializer.processingInstruction(name, pi.getData());
896 }
897 }
898
899 /**
900 * Serializes an CDATASection Node.
901 *
902 * @param node The CDATASection Node to serialize
903 */
904 protected void serializeCDATASection(CDATASection node)
905 throws SAXException {
906 // well-formed=true
907 if ((fFeatures & WELLFORMED) != 0) {
908 isCDATASectionWellFormed(node);
909 }
910
911 // cdata-sections = true
912 if ((fFeatures & CDATA) != 0) {
913
914 // split-cdata-sections = true
915 // Assumption: This parameter has an effect only when
916 // cdata-sections=true
917 // ToStream, by default splits cdata-sections. Hence the check
918 // below.
919 String nodeValue = node.getNodeValue();
920 int endIndex = nodeValue.indexOf("]]>");
921 if ((fFeatures & SPLITCDATA) != 0) {
922 if (endIndex >= 0) {
923 // The first node split will contain the ]] markers
924 String relatedData = nodeValue.substring(0, endIndex + 2);
925
926 String msg =
927 Utils.messages.createMessage(
928 MsgKey.ER_CDATA_SECTIONS_SPLIT,
929 null);
930
931 if (fErrorHandler != null) {
932 fErrorHandler.handleError(
933 new DOMErrorImpl(
934 DOMError.SEVERITY_WARNING,
935 msg,
936 MsgKey.ER_CDATA_SECTIONS_SPLIT,
937 null,
938 relatedData,
939 null));
940 }
941 }
942 } else {
943 if (endIndex >= 0) {
944 // The first node split will contain the ]] markers
945 String relatedData = nodeValue.substring(0, endIndex + 2);
946
947 String msg =
948 Utils.messages.createMessage(
949 MsgKey.ER_CDATA_SECTIONS_SPLIT,
950 null);
951
952 if (fErrorHandler != null) {
953 fErrorHandler.handleError(
954 new DOMErrorImpl(
955 DOMError.SEVERITY_ERROR,
956 msg,
957 MsgKey.ER_CDATA_SECTIONS_SPLIT));
958 }
959 // Report an error and return. What error???
960 return;
961 }
962 }
963
964 // apply the LSSerializer filter
965 if (!applyFilter(node, NodeFilter.SHOW_CDATA_SECTION)) {
966 return;
967 }
968
969 // splits the cdata-section
970 if (fLexicalHandler != null) {
971 fLexicalHandler.startCDATA();
972 }
973 dispatachChars(node);
974 if (fLexicalHandler != null) {
975 fLexicalHandler.endCDATA();
976 }
977 } else {
978 dispatachChars(node);
979 }
980 }
981
982 /**
983 * Serializes an Text Node.
984 *
985 * @param node The Text Node to serialize
986 */
987 protected void serializeText(Text node) throws SAXException {
988 if (fNextIsRaw) {
989 fNextIsRaw = false;
990 fSerializer.processingInstruction(
991 javax.xml.transform.Result.PI_DISABLE_OUTPUT_ESCAPING,
992 "");
993 dispatachChars(node);
994 fSerializer.processingInstruction(
995 javax.xml.transform.Result.PI_ENABLE_OUTPUT_ESCAPING,
996 "");
997 } else {
998 // keep track of dispatch or not to avoid duplicaiton of filter code
999 boolean bDispatch = false;
1000
1001 // well-formed=true
1002 if ((fFeatures & WELLFORMED) != 0) {
1003 isTextWellFormed(node);
1004 }
1005
1006 // if the node is whitespace
1007 // Determine the Attr's type.
1008 boolean isElementContentWhitespace = false;
1009 if (fIsLevel3DOM) {
1010 isElementContentWhitespace =
1011 node.isElementContentWhitespace();
1012 }
1013
1014 if (isElementContentWhitespace) {
1015 // element-content-whitespace=true
1016 if ((fFeatures & ELEM_CONTENT_WHITESPACE) != 0) {
1017 bDispatch = true;
1018 }
1019 } else {
1020 bDispatch = true;
1021 }
1022
1023 // apply the LSSerializer filter
1024 if (!applyFilter(node, NodeFilter.SHOW_TEXT)) {
1025 return;
1026 }
1027
1028 if (bDispatch
1029 && (!fSerializer.getIndent() || !node.getData().replace('\n', ' ').trim().isEmpty())) {
1030 dispatachChars(node);
1031 }
1032 }
1033 }
1034
1035 /**
1036 * Serializes an EntityReference Node.
1037 *
1038 * @param node The EntityReference Node to serialize
1039 * @param bStart Inicates if called from start or endNode
1040 */
1041 protected void serializeEntityReference(
1042 EntityReference node,
1043 boolean bStart)
1044 throws SAXException {
1045 if (bStart) {
1046 EntityReference eref = node;
1047 // entities=true
1048 if ((fFeatures & ENTITIES) != 0) {
1049
1050 // perform well-formedness and other checking only if
1051 // entities = true
1052
1053 // well-formed=true
1054 if ((fFeatures & WELLFORMED) != 0) {
1055 isEntityReferneceWellFormed(node);
1056 }
1057
1058 // check "unbound-prefix-in-entity-reference" [fatal]
1059 // Raised if the configuration parameter "namespaces" is set to true
1060 if ((fFeatures & NAMESPACES) != 0) {
1061 checkUnboundPrefixInEntRef(node);
1062 }
1063
1064 // The filter should not apply in this case, since the
1065 // EntityReference is not being expanded.
1066 // should we pass entity reference nodes to the filter???
1067 }
1068
1069 // if "entities" is true, or EntityReference node has no children,
1070 // it will be serialized as the form "&entityName;" in the output.
1071 if (fLexicalHandler != null && ((fFeatures & ENTITIES) != 0 || !node.hasChildNodes())) {
1072
1073 // startEntity outputs only Text but not Element, Attr, Comment
1074 // and PI child nodes. It does so by setting the m_inEntityRef
1075 // in ToStream and using this to decide if a node is to be
1076 // serialized or not.
1077 fLexicalHandler.startEntity(eref.getNodeName());
1078 }
1079
1080 } else {
1081 EntityReference eref = node;
1082 // entities=true or false,
1083 if (fLexicalHandler != null) {
1084 fLexicalHandler.endEntity(eref.getNodeName());
1085 }
1086 }
1087 }
1088
1089
1090 // ***********************************************************************
1091 // Methods to check well-formedness
1092 // ***********************************************************************
1093 /**
1094 * Taken from org.apache.xerces.dom.CoreDocumentImpl
1095 *
1096 * Check the string against XML's definition of acceptable names for
1097 * elements and attributes and so on using the XMLCharacterProperties
1098 * utility class
1099 */
1100 protected boolean isXMLName(String s, boolean xml11Version) {
1101
1102 if (s == null) {
1103 return false;
1104 }
1105 if (!xml11Version)
1106 return XMLChar.isValidName(s);
1107 else
1108 return XML11Char.isXML11ValidName(s);
1109 }
1110
1111 /**
1112 * Taken from org.apache.xerces.dom.CoreDocumentImpl
1113 *
1114 * Checks if the given qualified name is legal with respect
1115 * to the version of XML to which this document must conform.
1116 *
1117 * @param prefix prefix of qualified name
1118 * @param local local part of qualified name
1119 */
1120 protected boolean isValidQName(
1121 String prefix,
1122 String local,
1123 boolean xml11Version) {
1124
1125 // check that both prefix and local part match NCName
1126 if (local == null)
1127 return false;
1128 boolean validNCName = false;
1129
1130 if (!xml11Version) {
1131 validNCName =
1132 (prefix == null || XMLChar.isValidNCName(prefix))
1133 && XMLChar.isValidNCName(local);
1134 } else {
1135 validNCName =
1136 (prefix == null || XML11Char.isXML11ValidNCName(prefix))
1137 && XML11Char.isXML11ValidNCName(local);
1138 }
1139
1140 return validNCName;
1141 }
1142
1143 /**
1144 * Checks if a XML character is well-formed
1145 *
1146 * @param characters A String of characters to be checked for Well-Formedness
1147 * @param refInvalidChar A reference to the character to be returned that was determined invalid.
1148 */
1149 protected boolean isWFXMLChar(String chardata, Character refInvalidChar) {
1150 if (chardata == null || (chardata.length() == 0)) {
1151 return true;
1152 }
1153
1154 char[] dataarray = chardata.toCharArray();
1155 int datalength = dataarray.length;
1156
1157 // version of the document is XML 1.1
1158 if (fIsXMLVersion11) {
1159 //we need to check all characters as per production rules of XML11
1160 int i = 0;
1161 while (i < datalength) {
1162 if (XML11Char.isXML11Invalid(dataarray[i++])) {
1163 // check if this is a supplemental character
1164 char ch = dataarray[i - 1];
1165 if (XMLChar.isHighSurrogate(ch) && i < datalength) {
1166 char ch2 = dataarray[i++];
1167 if (XMLChar.isLowSurrogate(ch2)
1168 && XMLChar.isSupplemental(
1169 XMLChar.supplemental(ch, ch2))) {
1170 continue;
1171 }
1172 }
1173 // Reference to invalid character which is returned
1174 refInvalidChar = ch;
1175 return false;
1176 }
1177 }
1178 } // version of the document is XML 1.0
1179 else {
1180 // we need to check all characters as per production rules of XML 1.0
1181 int i = 0;
1182 while (i < datalength) {
1183 if (XMLChar.isInvalid(dataarray[i++])) {
1184 // check if this is a supplemental character
1185 char ch = dataarray[i - 1];
1186 if (XMLChar.isHighSurrogate(ch) && i < datalength) {
1187 char ch2 = dataarray[i++];
1188 if (XMLChar.isLowSurrogate(ch2)
1189 && XMLChar.isSupplemental(
1190 XMLChar.supplemental(ch, ch2))) {
1191 continue;
1192 }
1193 }
1194 // Reference to invalid character which is returned
1195 refInvalidChar = ch;
1196 return false;
1197 }
1198 }
1199 } // end-else fDocument.isXMLVersion()
1200
1201 return true;
1202 } // isXMLCharWF
1203
1204 /**
1205 * Checks if a XML character is well-formed. If there is a problem with
1206 * the character a non-null Character is returned else null is returned.
1207 *
1208 * @param characters A String of characters to be checked for Well-Formedness
1209 * @return Character A reference to the character to be returned that was determined invalid.
1210 */
1211 protected Character isWFXMLChar(String chardata) {
1212 Character refInvalidChar;
1213 if (chardata == null || (chardata.length() == 0)) {
1214 return null;
1215 }
1216
1217 char[] dataarray = chardata.toCharArray();
1218 int datalength = dataarray.length;
1219
1220 // version of the document is XML 1.1
1221 if (fIsXMLVersion11) {
1222 //we need to check all characters as per production rules of XML11
1223 int i = 0;
1224 while (i < datalength) {
1225 if (XML11Char.isXML11Invalid(dataarray[i++])) {
1226 // check if this is a supplemental character
1227 char ch = dataarray[i - 1];
1228 if (XMLChar.isHighSurrogate(ch) && i < datalength) {
1229 char ch2 = dataarray[i++];
1230 if (XMLChar.isLowSurrogate(ch2)
1231 && XMLChar.isSupplemental(
1232 XMLChar.supplemental(ch, ch2))) {
1233 continue;
1234 }
1235 }
1236 // Reference to invalid character which is returned
1237 refInvalidChar = ch;
1238 return refInvalidChar;
1239 }
1240 }
1241 } // version of the document is XML 1.0
1242 else {
1243 // we need to check all characters as per production rules of XML 1.0
1244 int i = 0;
1245 while (i < datalength) {
1246 if (XMLChar.isInvalid(dataarray[i++])) {
1247 // check if this is a supplemental character
1248 char ch = dataarray[i - 1];
1249 if (XMLChar.isHighSurrogate(ch) && i < datalength) {
1250 char ch2 = dataarray[i++];
1251 if (XMLChar.isLowSurrogate(ch2)
1252 && XMLChar.isSupplemental(
1253 XMLChar.supplemental(ch, ch2))) {
1254 continue;
1255 }
1256 }
1257 // Reference to invalid character which is returned
1258 refInvalidChar = ch;
1259 return refInvalidChar;
1260 }
1261 }
1262 } // end-else fDocument.isXMLVersion()
1263
1264 return null;
1265 } // isXMLCharWF
1266
1267 /**
1268 * Checks if a comment node is well-formed
1269 *
1270 * @param data The contents of the comment node
1271 * @return a boolean indiacating if the comment is well-formed or not.
1272 */
1273 protected void isCommentWellFormed(String data) {
1274 if (data == null || (data.length() == 0)) {
1275 return;
1276 }
1277
1278 char[] dataarray = data.toCharArray();
1279 int datalength = dataarray.length;
1280
1281 // version of the document is XML 1.1
1282 if (fIsXMLVersion11) {
1283 // we need to check all chracters as per production rules of XML11
1284 int i = 0;
1285 while (i < datalength) {
1286 char c = dataarray[i++];
1287 if (XML11Char.isXML11Invalid(c)) {
1288 // check if this is a supplemental character
1289 if (XMLChar.isHighSurrogate(c) && i < datalength) {
1290 char c2 = dataarray[i++];
1291 if (XMLChar.isLowSurrogate(c2)
1292 && XMLChar.isSupplemental(
1293 XMLChar.supplemental(c, c2))) {
1294 continue;
1295 }
1296 }
1297 String msg =
1298 Utils.messages.createMessage(
1299 MsgKey.ER_WF_INVALID_CHARACTER_IN_COMMENT,
1300 new Object[] { c});
1301
1302 if (fErrorHandler != null) {
1303 fErrorHandler.handleError(
1304 new DOMErrorImpl(
1305 DOMError.SEVERITY_FATAL_ERROR,
1306 msg,
1307 MsgKey.ER_WF_INVALID_CHARACTER,
1308 null,
1309 null,
1310 null));
1311 }
1312 } else if (c == '-' && i < datalength && dataarray[i] == '-') {
1313 String msg =
1314 Utils.messages.createMessage(
1315 MsgKey.ER_WF_DASH_IN_COMMENT,
1316 null);
1317
1318 if (fErrorHandler != null) {
1319 fErrorHandler.handleError(
1320 new DOMErrorImpl(
1321 DOMError.SEVERITY_FATAL_ERROR,
1322 msg,
1323 MsgKey.ER_WF_INVALID_CHARACTER,
1324 null,
1325 null,
1326 null));
1327 }
1328 }
1329 }
1330 } // version of the document is XML 1.0
1331 else {
1332 // we need to check all chracters as per production rules of XML 1.0
1333 int i = 0;
1334 while (i < datalength) {
1335 char c = dataarray[i++];
1336 if (XMLChar.isInvalid(c)) {
1337 // check if this is a supplemental character
1338 if (XMLChar.isHighSurrogate(c) && i < datalength) {
1339 char c2 = dataarray[i++];
1340 if (XMLChar.isLowSurrogate(c2)
1341 && XMLChar.isSupplemental(
1342 XMLChar.supplemental(c, c2))) {
1343 continue;
1344 }
1345 }
1346 String msg =
1347 Utils.messages.createMessage(
1348 MsgKey.ER_WF_INVALID_CHARACTER_IN_COMMENT,
1349 new Object[] { c});
1350
1351 if (fErrorHandler != null) {
1352 fErrorHandler.handleError(
1353 new DOMErrorImpl(
1354 DOMError.SEVERITY_FATAL_ERROR,
1355 msg,
1356 MsgKey.ER_WF_INVALID_CHARACTER,
1357 null,
1358 null,
1359 null));
1360 }
1361 } else if (c == '-' && i < datalength && dataarray[i] == '-') {
1362 String msg =
1363 Utils.messages.createMessage(
1364 MsgKey.ER_WF_DASH_IN_COMMENT,
1365 null);
1366
1367 if (fErrorHandler != null) {
1368 fErrorHandler.handleError(
1369 new DOMErrorImpl(
1370 DOMError.SEVERITY_FATAL_ERROR,
1371 msg,
1372 MsgKey.ER_WF_INVALID_CHARACTER,
1373 null,
1374 null,
1375 null));
1376 }
1377 }
1378 }
1379 }
1380 return;
1381 }
1382
1383 /**
1384 * Checks if an element node is well-formed, by checking its Name for well-formedness.
1385 *
1386 * @param data The contents of the comment node
1387 * @return a boolean indiacating if the comment is well-formed or not.
1388 */
1389 protected void isElementWellFormed(Node node) {
1390 boolean isNameWF = false;
1391 if ((fFeatures & NAMESPACES) != 0) {
1392 isNameWF =
1393 isValidQName(
1394 node.getPrefix(),
1395 node.getLocalName(),
1396 fIsXMLVersion11);
1397 } else {
1398 isNameWF = isXMLName(node.getNodeName(), fIsXMLVersion11);
1399 }
1400
1401 if (!isNameWF) {
1402 String msg =
1403 Utils.messages.createMessage(
1404 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1405 new Object[] { "Element", node.getNodeName()});
1406
1407 if (fErrorHandler != null) {
1408 fErrorHandler.handleError(
1409 new DOMErrorImpl(
1410 DOMError.SEVERITY_FATAL_ERROR,
1411 msg,
1412 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1413 null,
1414 null,
1415 null));
1416 }
1417 }
1418 }
1419
1420 /**
1421 * Checks if an attr node is well-formed, by checking it's Name and value
1422 * for well-formedness.
1423 *
1424 * @param data The contents of the comment node
1425 * @return a boolean indiacating if the comment is well-formed or not.
1426 */
1427 protected void isAttributeWellFormed(Node node) {
1428 boolean isNameWF = false;
1429 if ((fFeatures & NAMESPACES) != 0) {
1430 isNameWF =
1431 isValidQName(
1432 node.getPrefix(),
1433 node.getLocalName(),
1434 fIsXMLVersion11);
1435 } else {
1436 isNameWF = isXMLName(node.getNodeName(), fIsXMLVersion11);
1437 }
1438
1439 if (!isNameWF) {
1440 String msg =
1441 Utils.messages.createMessage(
1442 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1443 new Object[] { "Attr", node.getNodeName()});
1444
1445 if (fErrorHandler != null) {
1446 fErrorHandler.handleError(
1447 new DOMErrorImpl(
1448 DOMError.SEVERITY_FATAL_ERROR,
1449 msg,
1450 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1451 null,
1452 null,
1453 null));
1454 }
1455 }
1456
1457 // Check the Attr's node value
1458 // WFC: No < in Attribute Values
1459 String value = node.getNodeValue();
1460 if (value.indexOf('<') >= 0) {
1461 String msg =
1462 Utils.messages.createMessage(
1463 MsgKey.ER_WF_LT_IN_ATTVAL,
1464 new Object[] {
1465 ((Attr) node).getOwnerElement().getNodeName(),
1466 node.getNodeName()});
1467
1468 if (fErrorHandler != null) {
1469 fErrorHandler.handleError(
1470 new DOMErrorImpl(
1471 DOMError.SEVERITY_FATAL_ERROR,
1472 msg,
1473 MsgKey.ER_WF_LT_IN_ATTVAL,
1474 null,
1475 null,
1476 null));
1477 }
1478 }
1479
1480 // we need to loop through the children of attr nodes and check their values for
1481 // well-formedness
1482 NodeList children = node.getChildNodes();
1483 for (int i = 0; i < children.getLength(); i++) {
1484 Node child = children.item(i);
1485 // An attribute node with no text or entity ref child for example
1486 // doc.createAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:ns");
1487 // followes by
1488 // element.setAttributeNodeNS(attribute);
1489 // can potentially lead to this situation. If the attribute
1490 // was a prefix Namespace attribute declaration then then DOM Core
1491 // should have some exception defined for this.
1492 if (child == null) {
1493 // we should probably report an error
1494 continue;
1495 }
1496 switch (child.getNodeType()) {
1497 case Node.TEXT_NODE :
1498 isTextWellFormed((Text) child);
1499 break;
1500 case Node.ENTITY_REFERENCE_NODE :
1501 isEntityReferneceWellFormed((EntityReference) child);
1502 break;
1503 default :
1504 }
1505 }
1506
1507 // TODO:
1508 // WFC: Check if the attribute prefix is bound to
1509 // http://www.w3.org/2000/xmlns/
1510
1511 // WFC: Unique Att Spec
1512 // Perhaps pass a seen boolean value to this method. serializeAttList will determine
1513 // if the attr was seen before.
1514 }
1515
1516 /**
1517 * Checks if a PI node is well-formed, by checking it's Name and data
1518 * for well-formedness.
1519 *
1520 * @param data The contents of the comment node
1521 */
1522 protected void isPIWellFormed(ProcessingInstruction node) {
1523 // Is the PI Target a valid XML name
1524 if (!isXMLName(node.getNodeName(), fIsXMLVersion11)) {
1525 String msg =
1526 Utils.messages.createMessage(
1527 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1528 new Object[] { "ProcessingInstruction", node.getTarget()});
1529
1530 if (fErrorHandler != null) {
1531 fErrorHandler.handleError(
1532 new DOMErrorImpl(
1533 DOMError.SEVERITY_FATAL_ERROR,
1534 msg,
1535 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1536 null,
1537 null,
1538 null));
1539 }
1540 }
1541
1542 // Does the PI Data carry valid XML characters
1543
1544 // REVISIT: Should we check if the PI DATA contains a ?> ???
1545 Character invalidChar = isWFXMLChar(node.getData());
1546 if (invalidChar != null) {
1547 String msg =
1548 Utils.messages.createMessage(
1549 MsgKey.ER_WF_INVALID_CHARACTER_IN_PI,
1550 new Object[] { Integer.toHexString(Character.getNumericValue(invalidChar.charValue())) });
1551
1552 if (fErrorHandler != null) {
1553 fErrorHandler.handleError(
1554 new DOMErrorImpl(
1555 DOMError.SEVERITY_FATAL_ERROR,
1556 msg,
1557 MsgKey.ER_WF_INVALID_CHARACTER,
1558 null,
1559 null,
1560 null));
1561 }
1562 }
1563 }
1564
1565 /**
1566 * Checks if an CDATASection node is well-formed, by checking it's data
1567 * for well-formedness. Note that the presence of a CDATA termination mark
1568 * in the contents of a CDATASection is handled by the parameter
1569 * spli-cdata-sections
1570 *
1571 * @param data The contents of the comment node
1572 */
1573 protected void isCDATASectionWellFormed(CDATASection node) {
1574 // Does the data valid XML character data
1575 Character invalidChar = isWFXMLChar(node.getData());
1576 //if (!isWFXMLChar(node.getData(), invalidChar)) {
1577 if (invalidChar != null) {
1578 String msg =
1579 Utils.messages.createMessage(
1580 MsgKey.ER_WF_INVALID_CHARACTER_IN_CDATA,
1581 new Object[] { Integer.toHexString(Character.getNumericValue(invalidChar.charValue())) });
1582
1583 if (fErrorHandler != null) {
1584 fErrorHandler.handleError(
1585 new DOMErrorImpl(
1586 DOMError.SEVERITY_FATAL_ERROR,
1587 msg,
1588 MsgKey.ER_WF_INVALID_CHARACTER,
1589 null,
1590 null,
1591 null));
1592 }
1593 }
1594 }
1595
1596 /**
1597 * Checks if an Text node is well-formed, by checking if it contains invalid
1598 * XML characters.
1599 *
1600 * @param data The contents of the comment node
1601 */
1602 protected void isTextWellFormed(Text node) {
1603 // Does the data valid XML character data
1604 Character invalidChar = isWFXMLChar(node.getData());
1605 if (invalidChar != null) {
1606 String msg =
1607 Utils.messages.createMessage(
1608 MsgKey.ER_WF_INVALID_CHARACTER_IN_TEXT,
1609 new Object[] { Integer.toHexString(Character.getNumericValue(invalidChar.charValue())) });
1610
1611 if (fErrorHandler != null) {
1612 fErrorHandler.handleError(
1613 new DOMErrorImpl(
1614 DOMError.SEVERITY_FATAL_ERROR,
1615 msg,
1616 MsgKey.ER_WF_INVALID_CHARACTER,
1617 null,
1618 null,
1619 null));
1620 }
1621 }
1622 }
1623
1624 /**
1625 * Checks if an EntityRefernece node is well-formed, by checking it's node name. Then depending
1626 * on whether it is referenced in Element content or in an Attr Node, checks if the EntityReference
1627 * references an unparsed entity or a external entity and if so throws raises the
1628 * appropriate well-formedness error.
1629 *
1630 * @param data The contents of the comment node
1631 * @parent The parent of the EntityReference Node
1632 */
1633 protected void isEntityReferneceWellFormed(EntityReference node) {
1634 // Is the EntityReference name a valid XML name
1635 if (!isXMLName(node.getNodeName(), fIsXMLVersion11)) {
1636 String msg =
1637 Utils.messages.createMessage(
1638 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1639 new Object[] { "EntityReference", node.getNodeName()});
1640
1641 if (fErrorHandler != null) {
1642 fErrorHandler.handleError(
1643 new DOMErrorImpl(
1644 DOMError.SEVERITY_FATAL_ERROR,
1645 msg,
1646 MsgKey.ER_WF_INVALID_CHARACTER_IN_NODE_NAME,
1647 null,
1648 null,
1649 null));
1650 }
1651 }
1652
1653 // determine the parent node
1654 Node parent = node.getParentNode();
1655
1656 // Traverse the declared entities and check if the nodeName and namespaceURI
1657 // of the EntityReference matches an Entity. If so, check the if the notationName
1658 // is not null, if so, report an error.
1659 DocumentType docType = node.getOwnerDocument().getDoctype();
1660 if (docType != null) {
1661 NamedNodeMap entities = docType.getEntities();
1662 for (int i = 0; i < entities.getLength(); i++) {
1663 Entity ent = (Entity) entities.item(i);
1664
1665 String nodeName =
1666 node.getNodeName() == null ? "" : node.getNodeName();
1667 String nodeNamespaceURI =
1668 node.getNamespaceURI() == null
1669 ? ""
1670 : node.getNamespaceURI();
1671 String entName =
1672 ent.getNodeName() == null ? "" : ent.getNodeName();
1673 String entNamespaceURI =
1674 ent.getNamespaceURI() == null ? "" : ent.getNamespaceURI();
1675 // If referenced in Element content
1676 // WFC: Parsed Entity
1677 if (parent.getNodeType() == Node.ELEMENT_NODE) {
1678 if (entNamespaceURI.equals(nodeNamespaceURI)
1679 && entName.equals(nodeName)) {
1680
1681 if (ent.getNotationName() != null) {
1682 String msg =
1683 Utils.messages.createMessage(
1684 MsgKey.ER_WF_REF_TO_UNPARSED_ENT,
1685 new Object[] { node.getNodeName()});
1686
1687 if (fErrorHandler != null) {
1688 fErrorHandler.handleError(
1689 new DOMErrorImpl(
1690 DOMError.SEVERITY_FATAL_ERROR,
1691 msg,
1692 MsgKey.ER_WF_REF_TO_UNPARSED_ENT,
1693 null,
1694 null,
1695 null));
1696 }
1697 }
1698 }
1699 } // end if WFC: Parsed Entity
1700
1701 // If referenced in an Attr value
1702 // WFC: No External Entity References
1703 if (parent.getNodeType() == Node.ATTRIBUTE_NODE) {
1704 if (entNamespaceURI.equals(nodeNamespaceURI)
1705 && entName.equals(nodeName)) {
1706
1707 if (ent.getPublicId() != null
1708 || ent.getSystemId() != null
1709 || ent.getNotationName() != null) {
1710 String msg =
1711 Utils.messages.createMessage(
1712 MsgKey.ER_WF_REF_TO_EXTERNAL_ENT,
1713 new Object[] { node.getNodeName()});
1714
1715 if (fErrorHandler != null) {
1716 fErrorHandler.handleError(
1717 new DOMErrorImpl(
1718 DOMError.SEVERITY_FATAL_ERROR,
1719 msg,
1720 MsgKey.ER_WF_REF_TO_EXTERNAL_ENT,
1721 null,
1722 null,
1723 null));
1724 }
1725 }
1726 }
1727 } //end if WFC: No External Entity References
1728 }
1729 }
1730 } // isEntityReferneceWellFormed
1731
1732 /**
1733 * If the configuration parameter "namespaces" is set to true, this methods
1734 * checks if an entity whose replacement text contains unbound namespace
1735 * prefixes is referenced in a location where there are no bindings for
1736 * the namespace prefixes and if so raises a LSException with the error-type
1737 * "unbound-prefix-in-entity-reference"
1738 *
1739 * @param Node, The EntityReference nodes whose children are to be checked
1740 */
1741 protected void checkUnboundPrefixInEntRef(Node node) {
1742 Node child, next;
1743 for (child = node.getFirstChild(); child != null; child = next) {
1744 next = child.getNextSibling();
1745
1746 if (child.getNodeType() == Node.ELEMENT_NODE) {
1747
1748 //If a NamespaceURI is not declared for the current
1749 //node's prefix, raise a fatal error.
1750 String prefix = child.getPrefix();
1751 if (prefix != null
1752 && fNSBinder.getURI(prefix) == null) {
1753 String msg =
1754 Utils.messages.createMessage(
1755 MsgKey.ER_ELEM_UNBOUND_PREFIX_IN_ENTREF,
1756 new Object[] {
1757 node.getNodeName(),
1758 child.getNodeName(),
1759 prefix });
1760
1761 if (fErrorHandler != null) {
1762 fErrorHandler.handleError(
1763 new DOMErrorImpl(
1764 DOMError.SEVERITY_FATAL_ERROR,
1765 msg,
1766 MsgKey.ER_ELEM_UNBOUND_PREFIX_IN_ENTREF,
1767 null,
1768 null,
1769 null));
1770 }
1771 }
1772
1773 NamedNodeMap attrs = child.getAttributes();
1774
1775 for (int i = 0; i < attrs.getLength(); i++) {
1776 String attrPrefix = attrs.item(i).getPrefix();
1777 if (attrPrefix != null
1778 && fNSBinder.getURI(attrPrefix) == null) {
1779 String msg =
1780 Utils.messages.createMessage(
1781 MsgKey.ER_ATTR_UNBOUND_PREFIX_IN_ENTREF,
1782 new Object[] {
1783 node.getNodeName(),
1784 child.getNodeName(),
1785 attrs.item(i)});
1786
1787 if (fErrorHandler != null) {
1788 fErrorHandler.handleError(
1789 new DOMErrorImpl(
1790 DOMError.SEVERITY_FATAL_ERROR,
1791 msg,
1792 MsgKey.ER_ATTR_UNBOUND_PREFIX_IN_ENTREF,
1793 null,
1794 null,
1795 null));
1796 }
1797 }
1798 }
1799 }
1800
1801 if (child.hasChildNodes()) {
1802 checkUnboundPrefixInEntRef(child);
1803 }
1804 }
1805 }
1806
1807 // ***********************************************************************
1808 // Namespace normalization
1809 // ***********************************************************************
1810 /**
1811 * Records local namespace declarations, to be used for normalization later
1812 *
1813 * @param Node, The element node, whose namespace declarations are to be recorded
1814 */
1815 protected void recordLocalNSDecl(Node node) {
1816 NamedNodeMap atts = ((Element) node).getAttributes();
1817 int length = atts.getLength();
1818
1819 for (int i = 0; i < length; i++) {
1820 Node attr = atts.item(i);
1821
1822 String localName = attr.getLocalName();
1823 String attrPrefix = attr.getPrefix();
1824 String attrValue = attr.getNodeValue();
1825 String attrNS = attr.getNamespaceURI();
1826
1827 localName =
1828 localName == null
1829 || XMLNS_PREFIX.equals(localName) ? "" : localName;
1830 attrPrefix = attrPrefix == null ? "" : attrPrefix;
1831 attrValue = attrValue == null ? "" : attrValue;
1832 attrNS = attrNS == null ? "" : attrNS;
1833
1834 // check if attribute is a namespace decl
1835 if (XMLNS_URI.equals(attrNS)) {
1836
1837 // No prefix may be bound to http://www.w3.org/2000/xmlns/.
1838 if (XMLNS_URI.equals(attrValue)) {
1839 String msg =
1840 Utils.messages.createMessage(
1841 MsgKey.ER_NS_PREFIX_CANNOT_BE_BOUND,
1842 new Object[] { attrPrefix, XMLNS_URI });
1843
1844 if (fErrorHandler != null) {
1845 fErrorHandler.handleError(
1846 new DOMErrorImpl(
1847 DOMError.SEVERITY_ERROR,
1848 msg,
1849 MsgKey.ER_NS_PREFIX_CANNOT_BE_BOUND,
1850 null,
1851 null,
1852 null));
1853 }
1854 } else {
1855 // store the namespace-declaration
1856 if (XMLNS_PREFIX.equals(attrPrefix) ) {
1857 // record valid decl
1858 if (attrValue.length() != 0) {
1859 fNSBinder.declarePrefix(localName, attrValue);
1860 } else {
1861 // Error; xmlns:prefix=""
1862 }
1863 } else { // xmlns
1864 // empty prefix is always bound ("" or some string)
1865 fNSBinder.declarePrefix("", attrValue);
1866 }
1867 }
1868
1869 }
1870 }
1871 }
1872
1873 /**
1874 * Fixes an element's namespace
1875 *
1876 * @param Node, The element node, whose namespace is to be fixed
1877 */
1878 protected void fixupElementNS(Node node) throws SAXException {
1879 String namespaceURI = ((Element) node).getNamespaceURI();
1880 String prefix = ((Element) node).getPrefix();
1881 String localName = ((Element) node).getLocalName();
1882
1883 if (namespaceURI != null) {
1884 //if ( Element's prefix/namespace pair (or default namespace,
1885 // if no prefix) are within the scope of a binding )
1886 prefix = prefix == null ? "" : prefix;
1887 String inScopeNamespaceURI = fNSBinder.getURI(prefix);
1888
1889 if ((inScopeNamespaceURI != null
1890 && inScopeNamespaceURI.equals(namespaceURI))) {
1891 // do nothing, declaration in scope is inherited
1892
1893 } else {
1894 // Create a local namespace declaration attr for this namespace,
1895 // with Element's current prefix (or a default namespace, if
1896 // no prefix). If there's a conflicting local declaration
1897 // already present, change its value to use this namespace.
1898
1899 // Add the xmlns declaration attribute
1900 //fNSBinder.pushNamespace(prefix, namespaceURI, fElementDepth);
1901 if ((fFeatures & NAMESPACEDECLS) != 0) {
1902 if ("".equals(prefix) || "".equals(namespaceURI)) {
1903 ((Element)node).setAttributeNS(XMLNS_URI, XMLNS_PREFIX, namespaceURI);
1904 } else {
1905 ((Element)node).setAttributeNS(XMLNS_URI, XMLNS_PREFIX + ":" + prefix, namespaceURI);
1906 }
1907 }
1908 fLocalNSBinder.declarePrefix(prefix, namespaceURI);
1909 fNSBinder.declarePrefix(prefix, namespaceURI);
1910
1911 }
1912 } else {
1913 // Element has no namespace
1914 // DOM Level 1
1915 if (localName == null || "".equals(localName)) {
1916 // DOM Level 1 node!
1917 String msg =
1918 Utils.messages.createMessage(
1919 MsgKey.ER_NULL_LOCAL_ELEMENT_NAME,
1920 new Object[] { node.getNodeName()});
1921
1922 if (fErrorHandler != null) {
1923 fErrorHandler.handleError(
1924 new DOMErrorImpl(
1925 DOMError.SEVERITY_ERROR,
1926 msg,
1927 MsgKey.ER_NULL_LOCAL_ELEMENT_NAME,
1928 null,
1929 null,
1930 null));
1931 }
1932 } else {
1933 namespaceURI = fNSBinder.getURI("");
1934 if (namespaceURI !=null && namespaceURI.length() > 0) {
1935 ((Element)node).setAttributeNS(XMLNS_URI, XMLNS_PREFIX, "");
1936 fLocalNSBinder.declarePrefix("", "");
1937 fNSBinder.declarePrefix("", "");
1938 }
1939 }
1940 }
1941 }
1942 /**
1943 * This table is a quick lookup of a property key (String) to the integer that
1944 * is the bit to flip in the fFeatures field, so the integers should have
1945 * values 1,2,4,8,16...
1946 *
1947 */
1948 private static final Map<String, Integer> fFeatureMap;
1949 static {
1950
1951 // Initialize the mappings of property keys to bit values (Integer objects)
1952 // or mappings to a String object "", which indicates we are interested
1953 // in the property, but it does not have a simple bit value to flip
1954
1955 Map<String, Integer> featureMap = new HashMap<>();
1956 // cdata-sections
1957 featureMap.put(
1958 DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_CDATA_SECTIONS,
1959 CDATA);
1960
1961 // comments
1962 featureMap.put(
1963 DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_COMMENTS,
1964 COMMENTS);
1965
1966 // element-content-whitespace
1967 featureMap.put(
1968 DOMConstants.S_DOM3_PROPERTIES_NS
1969 + DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE,
1970 ELEM_CONTENT_WHITESPACE);
1971
1972 // entities
1973 featureMap.put(
1974 DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_ENTITIES,
1975 ENTITIES);
1976
1977 // namespaces
1978 featureMap.put(
1979 DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_NAMESPACES,
1980 NAMESPACES);
1981
1982 // namespace-declarations
1983 featureMap.put(
1984 DOMConstants.S_DOM3_PROPERTIES_NS
1985 + DOMConstants.DOM_NAMESPACE_DECLARATIONS,
1986 NAMESPACEDECLS);
1987
1988 // split-cdata-sections
1989 featureMap.put(
1990 DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_SPLIT_CDATA,
1991 SPLITCDATA);
1992
1993 // discard-default-content
1994 featureMap.put(
1995 DOMConstants.S_DOM3_PROPERTIES_NS + DOMConstants.DOM_WELLFORMED,
1996 WELLFORMED);
1997
1998 // discard-default-content
1999 featureMap.put(
2000 DOMConstants.S_DOM3_PROPERTIES_NS
2001 + DOMConstants.DOM_DISCARD_DEFAULT_CONTENT,
2002 DISCARDDEFAULT);
2003
2004 fFeatureMap = Collections.unmodifiableMap(featureMap);
2005 }
2006
2007 /**
2008 * Initializes fFeatures based on the DOMConfiguration Parameters set.
2009 *
2010 * @param properties DOMConfiguraiton properties that were set and which are
2011 * to be used while serializing the DOM.
2012 */
2013 protected void initProperties(Properties properties) {
2014 for(String key : properties.stringPropertyNames()) {
2015
2016 // caonical-form
2017 // Other features will be enabled or disabled when this is set to true or false.
2018
2019 // error-handler; set via the constructor
2020
2021 // infoset
2022 // Other features will be enabled or disabled when this is set to true
2023
2024 // A quick lookup for the given set of properties (cdata-sections ...)
2025 final Integer bitFlag = fFeatureMap.get(key);
2026 if (bitFlag != null) {
2027 // Dealing with a property that has a simple bit value that
2028 // we need to set
2029
2030 // cdata-sections
2031 // comments
2032 // element-content-whitespace
2033 // entities
2034 // namespaces
2035 // namespace-declarations
2036 // split-cdata-sections
2037 // well-formed
2038 // discard-default-content
2039 if ((properties.getProperty(key).endsWith("yes"))) {
2040 fFeatures = fFeatures | bitFlag;
2041 } else {
2042 fFeatures = fFeatures & ~bitFlag;
2043 }
2044 } else {
2045 /**
2046 * Other properties that have a bit more complex value
2047 * than the features in the above map.
2048 */
2049 if ((DOMConstants.S_DOM3_PROPERTIES_NS
2050 + DOMConstants.DOM_FORMAT_PRETTY_PRINT)
2051 .equals(key)) {
2052 // format-pretty-print; set internally on the serializers via xsl:output properties in LSSerializer
2053 if ((properties.getProperty(key).endsWith("yes"))) {
2054 fSerializer.setIndent(true);
2055 fSerializer.setIndentAmount(4);
2056 } else {
2057 fSerializer.setIndent(false);
2058 }
2059 } else if ((DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL).equals(key)) {
2060 // omit-xml-declaration; set internally on the serializers via xsl:output properties in LSSerializer
2061 if ((properties.getProperty(key).endsWith("yes"))) {
2062 fSerializer.setOmitXMLDeclaration(true);
2063 } else {
2064 fSerializer.setOmitXMLDeclaration(false);
2065 }
2066 } else if ((DOMConstants.S_XERCES_PROPERTIES_NS
2067 + DOMConstants.S_XML_VERSION).equals(key)) {
2068 // Retreive the value of the XML Version attribute via the xml-version
2069 String version = properties.getProperty(key);
2070 if ("1.1".equals(version)) {
2071 fIsXMLVersion11 = true;
2072 fSerializer.setVersion(version);
2073 } else {
2074 fSerializer.setVersion("1.0");
2075 }
2076 } else if ((DOMConstants.S_XSL_OUTPUT_ENCODING).equals(key)) {
2077 // Retreive the value of the XML Encoding attribute
2078 String encoding = properties.getProperty(key);
2079 if (encoding != null) {
2080 fSerializer.setEncoding(encoding);
2081 }
2082 } else if ((OutputPropertiesFactory.S_KEY_ENTITIES).equals(key)) {
2083 // Retreive the value of the XML Encoding attribute
2084 String entities = properties.getProperty(key);
2085 if (DOMConstants.S_XSL_VALUE_ENTITIES.equals(entities)) {
2086 fSerializer.setDTDEntityExpansion(false);
2087 }
2088 }
2089 }
2090 }
2091 // Set the newLine character to use
2092 if (fNewLine != null) {
2093 fSerializer.setOutputProperty(OutputPropertiesFactory.S_KEY_LINE_SEPARATOR, fNewLine);
2094 }
2095 }
2096
2097 } //TreeWalker
--- EOF ---