1 /*
2  * reserved comment block
3  * DO NOT REMOVE OR ALTER!
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.xerces.internal.impl.xs.opti;
23 
24 import java.io.IOException;
25 
26 import com.sun.org.apache.xerces.internal.impl.Constants;
27 import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
28 import com.sun.org.apache.xerces.internal.impl.xs.SchemaSymbols;
29 import com.sun.org.apache.xerces.internal.impl.xs.XSMessageFormatter;
30 import com.sun.org.apache.xerces.internal.util.XMLAttributesImpl;
31 import com.sun.org.apache.xerces.internal.util.XMLChar;
32 import com.sun.org.apache.xerces.internal.xni.Augmentations;
33 import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
34 import com.sun.org.apache.xerces.internal.xni.QName;
35 import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
36 import com.sun.org.apache.xerces.internal.xni.XMLLocator;
37 import com.sun.org.apache.xerces.internal.xni.XMLString;
38 import com.sun.org.apache.xerces.internal.xni.XNIException;
39 import com.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver;
40 import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
41 import com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration;
42 import org.w3c.dom.Document;
43 
44 /**
45  * @xerces.internal
46  *
47  * @author Rahul Srivastava, Sun Microsystems Inc.
48  * @author Sandy Gao, IBM
49  *
50  */
51 public class SchemaDOMParser extends DefaultXMLDocumentHandler {
52 
53     //
54     // Data
55     //
56 
57     /** Property identifier: error reporter. */
58     public static final String ERROR_REPORTER =
59         Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
60 
61     /** Feature identifier: generate synthetic annotations. */
62     public static final String GENERATE_SYNTHETIC_ANNOTATION =
63         Constants.XERCES_FEATURE_PREFIX + Constants.GENERATE_SYNTHETIC_ANNOTATIONS_FEATURE;
64 
65     // the locator containing line/column information
66     protected XMLLocator   fLocator;
67 
68     // namespace context, needed for producing
69     // representations of annotations
70     protected NamespaceContext fNamespaceContext = null;
71 
72     SchemaDOM schemaDOM;
73 
74     XMLParserConfiguration config;
75 
76     //
77     // Constructors
78     //
79 
80     /** Default constructor. */
SchemaDOMParser(XMLParserConfiguration config)81     public SchemaDOMParser(XMLParserConfiguration config) {
82         this.config = config;
83         config.setDocumentHandler(this);
84         config.setDTDHandler(this);
85         config.setDTDContentModelHandler(this);
86     }
87 
88     // Reference to the current annotation element.
89     private ElementImpl fCurrentAnnotationElement;
90     // where an annotation element itself begins
91     // -1 means not in an annotation's scope
92     private int fAnnotationDepth = -1;
93     // Where xs:appinfo or xs:documentation starts;
94     // -1 means not in the scope of either of the two elements.
95     private int fInnerAnnotationDepth = -1;
96     // The current element depth
97     private int fDepth = -1;
98     // Use to report the error when characters are not allowed.
99     XMLErrorReporter fErrorReporter;
100 
101     // fields for generate-synthetic annotations feature
102     private boolean fGenerateSyntheticAnnotation = false;
103     private BooleanStack fHasNonSchemaAttributes = new BooleanStack();
104     private BooleanStack fSawAnnotation = new BooleanStack();
105     private XMLAttributes fEmptyAttr = new XMLAttributesImpl();
106 
107     //
108     // XMLDocumentHandler methods
109     //
110 
startDocument(XMLLocator locator, String encoding, NamespaceContext namespaceContext, Augmentations augs)111     public void startDocument(XMLLocator locator, String encoding,
112             NamespaceContext namespaceContext, Augmentations augs)
113     throws XNIException {
114         fErrorReporter = (XMLErrorReporter)config.getProperty(ERROR_REPORTER);
115         fGenerateSyntheticAnnotation = config.getFeature(GENERATE_SYNTHETIC_ANNOTATION);
116         fHasNonSchemaAttributes.clear();
117         fSawAnnotation.clear();
118         schemaDOM = new SchemaDOM();
119         fCurrentAnnotationElement = null;
120         fAnnotationDepth = -1;
121         fInnerAnnotationDepth = -1;
122         fDepth = -1;
123         fLocator = locator;
124         fNamespaceContext = namespaceContext;
125         schemaDOM.setDocumentURI(locator.getExpandedSystemId());
126     } // startDocument(XMLLocator,String,NamespaceContext, Augmentations)
127 
128     /**
129      * The end of the document.
130      * @param augs     Additional information that may include infoset augmentations
131      *
132      * @throws XNIException Thrown by handler to signal an error.
133      */
endDocument(Augmentations augs)134     public void endDocument(Augmentations augs) throws XNIException {
135         // To debug the DOM created uncomment the line below
136         // schemaDOM.printDOM();
137     } // endDocument()
138 
139 
140     /**
141      * A comment.
142      *
143      * @param text   The text in the comment.
144      * @param augs   Additional information that may include infoset augmentations
145      *
146      * @exception XNIException
147      *                   Thrown by application to signal an error.
148      */
comment(XMLString text, Augmentations augs)149     public void comment(XMLString text, Augmentations augs) throws XNIException {
150         if(fAnnotationDepth > -1) {
151             schemaDOM.comment(text);
152         }
153     }
154 
155     /**
156      * A processing instruction. Processing instructions consist of a
157      * target name and, optionally, text data. The data is only meaningful
158      * to the application.
159      * <p>
160      * Typically, a processing instruction's data will contain a series
161      * of pseudo-attributes. These pseudo-attributes follow the form of
162      * element attributes but are <strong>not</strong> parsed or presented
163      * to the application as anything other than text. The application is
164      * responsible for parsing the data.
165      *
166      * @param target The target.
167      * @param data   The data or null if none specified.
168      * @param augs   Additional information that may include infoset augmentations
169      *
170      * @exception XNIException
171      *                   Thrown by handler to signal an error.
172      */
processingInstruction(String target, XMLString data, Augmentations augs)173     public void processingInstruction(String target, XMLString data, Augmentations augs)
174     throws XNIException {
175         if (fAnnotationDepth > -1) {
176             schemaDOM.processingInstruction(target, data);
177         }
178     }
179 
180     /**
181      * Character content.
182      *
183      * @param text   The content.
184      * @param augs   Additional information that may include infoset augmentations
185      *
186      * @exception XNIException
187      *                   Thrown by handler to signal an error.
188      */
characters(XMLString text, Augmentations augs)189     public void characters(XMLString text, Augmentations augs) throws XNIException {
190         // when it's not within xs:appinfo or xs:documentation
191         if (fInnerAnnotationDepth == -1 ) {
192             for (int i=text.offset; i<text.offset+text.length; i++) {
193                 // and there is a non-whitespace character
194                 if (!XMLChar.isSpace(text.ch[i])) {
195                     // the string we saw: starting from the first non-whitespace character.
196                     String txt = new String(text.ch, i, text.length+text.offset-i);
197                     // report an error
198                     fErrorReporter.reportError(fLocator,
199                             XSMessageFormatter.SCHEMA_DOMAIN,
200                             "s4s-elt-character",
201                             new Object[]{txt},
202                             XMLErrorReporter.SEVERITY_ERROR);
203                     break;
204                 }
205             }
206             // don't call super.characters() when it's not within one of the 2
207             // annotation elements: the traversers ignore them anyway. We can
208             // save time/memory creating the text nodes.
209         }
210         // when it's within either of the 2 elements, characters are allowed
211         // and we need to store them.
212         else {
213             schemaDOM.characters(text);
214         }
215 
216     }
217 
218 
219     /**
220      * The start of an element.
221      *
222      * @param element    The name of the element.
223      * @param attributes The element attributes.
224      * @param augs       Additional information that may include infoset augmentations
225      *
226      * @exception XNIException
227      *                   Thrown by handler to signal an error.
228      */
startElement(QName element, XMLAttributes attributes, Augmentations augs)229     public void startElement(QName element, XMLAttributes attributes, Augmentations augs)
230     throws XNIException {
231 
232         fDepth++;
233         // while it is true that non-whitespace character data
234         // may only occur in appInfo or documentation
235         // elements, it's certainly legal for comments and PI's to
236         // occur as children of annotation; we need
237         // to account for these here.
238         if (fAnnotationDepth == -1) {
239             if (element.uri == SchemaSymbols.URI_SCHEMAFORSCHEMA &&
240                     element.localpart == SchemaSymbols.ELT_ANNOTATION) {
241                 if (fGenerateSyntheticAnnotation) {
242                     if (fSawAnnotation.size() > 0) {
243                         fSawAnnotation.pop();
244                     }
245                     fSawAnnotation.push(true);
246                 }
247                 fAnnotationDepth = fDepth;
248                 schemaDOM.startAnnotation(element, attributes, fNamespaceContext);
249                 fCurrentAnnotationElement = schemaDOM.startElement(element, attributes,
250                         fLocator.getLineNumber(),
251                         fLocator.getColumnNumber(),
252                         fLocator.getCharacterOffset());
253                 return;
254             }
255             else if (element.uri == SchemaSymbols.URI_SCHEMAFORSCHEMA && fGenerateSyntheticAnnotation) {
256                 fSawAnnotation.push(false);
257                 fHasNonSchemaAttributes.push(hasNonSchemaAttributes(element, attributes));
258             }
259         }
260         else if (fDepth == fAnnotationDepth + 1) {
261             fInnerAnnotationDepth = fDepth;
262             schemaDOM.startAnnotationElement(element, attributes);
263         }
264         else {
265             schemaDOM.startAnnotationElement(element, attributes);
266             // avoid falling through; don't call startElement in this case
267             return;
268         }
269         schemaDOM.startElement(element, attributes,
270                 fLocator.getLineNumber(),
271                 fLocator.getColumnNumber(),
272                 fLocator.getCharacterOffset());
273 
274     }
275 
276 
277     /**
278      * An empty element.
279      *
280      * @param element    The name of the element.
281      * @param attributes The element attributes.
282      * @param augs       Additional information that may include infoset augmentations
283      *
284      * @exception XNIException
285      *                   Thrown by handler to signal an error.
286      */
emptyElement(QName element, XMLAttributes attributes, Augmentations augs)287     public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs)
288     throws XNIException {
289 
290         if (fGenerateSyntheticAnnotation && fAnnotationDepth == -1 &&
291                 element.uri == SchemaSymbols.URI_SCHEMAFORSCHEMA && element.localpart != SchemaSymbols.ELT_ANNOTATION && hasNonSchemaAttributes(element, attributes)) {
292 
293             schemaDOM.startElement(element, attributes,
294                     fLocator.getLineNumber(),
295                     fLocator.getColumnNumber(),
296                     fLocator.getCharacterOffset());
297 
298             attributes.removeAllAttributes();
299             String schemaPrefix = fNamespaceContext.getPrefix(SchemaSymbols.URI_SCHEMAFORSCHEMA);
300             final String annRawName = (schemaPrefix.length() == 0) ? SchemaSymbols.ELT_ANNOTATION : (schemaPrefix + ':' + SchemaSymbols.ELT_ANNOTATION);
301             schemaDOM.startAnnotation(annRawName, attributes, fNamespaceContext);
302             final String elemRawName = (schemaPrefix.length() == 0) ? SchemaSymbols.ELT_DOCUMENTATION : (schemaPrefix + ':' + SchemaSymbols.ELT_DOCUMENTATION);
303             schemaDOM.startAnnotationElement(elemRawName, attributes);
304             schemaDOM.charactersRaw("SYNTHETIC_ANNOTATION");
305             schemaDOM.endSyntheticAnnotationElement(elemRawName, false);
306             schemaDOM.endSyntheticAnnotationElement(annRawName, true);
307 
308             schemaDOM.endElement();
309 
310             return;
311         }
312         // the order of events that occurs here is:
313         //   schemaDOM.startAnnotation/startAnnotationElement (if applicable)
314         //   schemaDOM.emptyElement  (basically the same as startElement then endElement)
315         //   schemaDOM.endAnnotationElement (if applicable)
316         // the order of events that would occur if this was <element></element>:
317         //   schemaDOM.startAnnotation/startAnnotationElement (if applicable)
318         //   schemaDOM.startElement
319         //   schemaDOM.endAnnotationElement (if applicable)
320         //   schemaDOM.endElementElement
321         // Thus, we can see that the order of events isn't the same.  However, it doesn't
322         // seem to matter.  -- PJM
323         if (fAnnotationDepth == -1) {
324             // this is messed up, but a case to consider:
325             if (element.uri == SchemaSymbols.URI_SCHEMAFORSCHEMA &&
326                     element.localpart == SchemaSymbols.ELT_ANNOTATION) {
327                 schemaDOM.startAnnotation(element, attributes, fNamespaceContext);
328             }
329         }
330         else {
331             schemaDOM.startAnnotationElement(element, attributes);
332         }
333 
334         ElementImpl newElem = schemaDOM.emptyElement(element, attributes,
335                 fLocator.getLineNumber(),
336                 fLocator.getColumnNumber(),
337                 fLocator.getCharacterOffset());
338 
339         if (fAnnotationDepth == -1) {
340             // this is messed up, but a case to consider:
341             if (element.uri == SchemaSymbols.URI_SCHEMAFORSCHEMA &&
342                     element.localpart == SchemaSymbols.ELT_ANNOTATION) {
343                 schemaDOM.endAnnotation(element, newElem);
344             }
345         }
346         else {
347             schemaDOM.endAnnotationElement(element);
348         }
349     }
350 
351 
352     /**
353      * The end of an element.
354      *
355      * @param element The name of the element.
356      * @param augs    Additional information that may include infoset augmentations
357      *
358      * @exception XNIException
359      *                   Thrown by handler to signal an error.
360      */
endElement(QName element, Augmentations augs)361     public void endElement(QName element, Augmentations augs) throws XNIException {
362 
363         // when we reach the endElement of xs:appinfo or xs:documentation,
364         // change fInnerAnnotationDepth to -1
365         if(fAnnotationDepth > -1) {
366             if (fInnerAnnotationDepth == fDepth) {
367                 fInnerAnnotationDepth = -1;
368                 schemaDOM.endAnnotationElement(element);
369                 schemaDOM.endElement();
370             } else if (fAnnotationDepth == fDepth) {
371                 fAnnotationDepth = -1;
372                 schemaDOM.endAnnotation(element, fCurrentAnnotationElement);
373                 schemaDOM.endElement();
374             } else { // inside a child of annotation
375                 schemaDOM.endAnnotationElement(element);
376             }
377         } else { // not in an annotation at all
378             if(element.uri == SchemaSymbols.URI_SCHEMAFORSCHEMA && fGenerateSyntheticAnnotation) {
379                 boolean value = fHasNonSchemaAttributes.pop();
380                 boolean sawann = fSawAnnotation.pop();
381                 if (value && !sawann) {
382                     String schemaPrefix = fNamespaceContext.getPrefix(SchemaSymbols.URI_SCHEMAFORSCHEMA);
383                     final String annRawName = (schemaPrefix.length() == 0) ? SchemaSymbols.ELT_ANNOTATION : (schemaPrefix + ':' + SchemaSymbols.ELT_ANNOTATION);
384                     schemaDOM.startAnnotation(annRawName, fEmptyAttr, fNamespaceContext);
385                     final String elemRawName = (schemaPrefix.length() == 0) ? SchemaSymbols.ELT_DOCUMENTATION : (schemaPrefix + ':' + SchemaSymbols.ELT_DOCUMENTATION);
386                     schemaDOM.startAnnotationElement(elemRawName, fEmptyAttr);
387                     schemaDOM.charactersRaw("SYNTHETIC_ANNOTATION");
388                     schemaDOM.endSyntheticAnnotationElement(elemRawName, false);
389                     schemaDOM.endSyntheticAnnotationElement(annRawName, true);
390                 }
391             }
392             schemaDOM.endElement();
393         }
394         fDepth--;
395 
396     }
397 
398     /**
399      * @param attributes
400      * @return
401      */
hasNonSchemaAttributes(QName element, XMLAttributes attributes)402     private boolean hasNonSchemaAttributes(QName element, XMLAttributes attributes) {
403         final int length = attributes.getLength();
404         for (int i = 0; i < length; ++i) {
405             String uri = attributes.getURI(i);
406             if (uri != null && uri != SchemaSymbols.URI_SCHEMAFORSCHEMA &&
407                     uri != NamespaceContext.XMLNS_URI &&
408                     !(uri == NamespaceContext.XML_URI &&
409                             attributes.getQName(i) == SchemaSymbols.ATT_XML_LANG && element.localpart == SchemaSymbols.ELT_SCHEMA)) {
410                 return true;
411             }
412         }
413         return false;
414     }
415 
416     /**
417      * Ignorable whitespace. For this method to be called, the document
418      * source must have some way of determining that the text containing
419      * only whitespace characters should be considered ignorable. For
420      * example, the validator can determine if a length of whitespace
421      * characters in the document are ignorable based on the element
422      * content model.
423      *
424      * @param text   The ignorable whitespace.
425      * @param augs   Additional information that may include infoset augmentations
426      *
427      * @exception XNIException
428      *                   Thrown by handler to signal an error.
429      */
ignorableWhitespace(XMLString text, Augmentations augs)430     public void ignorableWhitespace(XMLString text, Augmentations augs) throws XNIException {
431         // unlikely to be called, but you never know...
432         if (fAnnotationDepth != -1 ) {
433             schemaDOM.characters(text);
434         }
435     }
436 
437     /**
438      * The start of a CDATA section.
439      *
440      * @param augs   Additional information that may include infoset augmentations
441      *
442      * @exception XNIException
443      *                   Thrown by handler to signal an error.
444      */
startCDATA(Augmentations augs)445     public void startCDATA(Augmentations augs) throws XNIException {
446         // only deal with CDATA boundaries within an annotation.
447         if (fAnnotationDepth != -1) {
448             schemaDOM.startAnnotationCDATA();
449         }
450     }
451 
452     /**
453      * The end of a CDATA section.
454      *
455      * @param augs   Additional information that may include infoset augmentations
456      *
457      * @exception XNIException
458      *                   Thrown by handler to signal an error.
459      */
endCDATA(Augmentations augs)460     public void endCDATA(Augmentations augs) throws XNIException {
461         // only deal with CDATA boundaries within an annotation.
462         if (fAnnotationDepth != -1) {
463             schemaDOM.endAnnotationCDATA();
464         }
465     }
466 
467 
468     //
469     // other methods
470     //
471 
472     /**
473      * Returns the DOM document object.
474      */
getDocument()475     public Document getDocument() {
476         return schemaDOM;
477     }
478 
479     /**
480      * Delegates to SchemaParsingConfig.setFeature
481      * @param featureId
482      * @param state
483      */
setFeature(String featureId, boolean state)484     public void setFeature(String featureId, boolean state){
485         config.setFeature(featureId, state);
486     }
487 
488     /**
489      * Delegates to SchemaParsingConfig.getFeature
490      * @param featureId
491      * @return boolean
492      */
getFeature(String featureId)493     public boolean getFeature(String featureId){
494         return config.getFeature(featureId);
495     }
496 
497     /**
498      * Delegates to SchemaParsingConfig.setProperty.
499      * @param propertyId
500      * @param value
501      */
setProperty(String propertyId, Object value)502     public void setProperty(String propertyId, Object value){
503         config.setProperty(propertyId, value);
504     }
505 
506     /**
507      * Delegates to SchemaParsingConfig.getProperty.
508      * @param propertyId
509      * @return Object
510      */
getProperty(String propertyId)511     public Object getProperty(String propertyId){
512         return config.getProperty(propertyId);
513     }
514 
515     /**
516      * Delegates to SchemaParsingConfig.setEntityResolver.
517      * @param er XMLEntityResolver
518      */
setEntityResolver(XMLEntityResolver er)519     public void setEntityResolver(XMLEntityResolver er) {
520         config.setEntityResolver(er);
521     }
522 
523     /**
524      * Delegates parsing to SchemaParsingConfig
525      *
526      * @param inputSource
527      * @throws IOException
528      */
parse(XMLInputSource inputSource)529     public void parse(XMLInputSource inputSource) throws IOException {
530         config.parse(inputSource);
531     }
532 
533     /**
534      * Reset SchemaParsingConfig
535      */
reset()536     public void reset() {
537         ((SchemaParsingConfig)config).reset();
538     }
539 
540     /**
541      * ResetNodePool on SchemaParsingConfig
542      */
resetNodePool()543     public void resetNodePool() {
544         ((SchemaParsingConfig)config).resetNodePool();
545     }
546 
547     /**
548      * A simple boolean based stack.
549      *
550      * @xerces.internal
551      */
552     private static final class BooleanStack {
553 
554         //
555         // Data
556         //
557 
558         /** Stack depth. */
559         private int fDepth;
560 
561         /** Stack data. */
562         private boolean[] fData;
563 
564         //
565         // Constructor
566         //
567 
BooleanStack()568         public BooleanStack () {}
569 
570         //
571         // Public methods
572         //
573 
574         /** Returns the size of the stack. */
size()575         public int size() {
576             return fDepth;
577         }
578 
579         /** Pushes a value onto the stack. */
push(boolean value)580         public void push(boolean value) {
581             ensureCapacity(fDepth + 1);
582             fData[fDepth++] = value;
583         }
584 
585         /** Pops a value off of the stack. */
pop()586         public boolean pop() {
587             return fData[--fDepth];
588         }
589 
590         /** Clears the stack. */
clear()591         public void clear() {
592             fDepth = 0;
593         }
594 
595         //
596         // Private methods
597         //
598 
599         /** Ensures capacity. */
ensureCapacity(int size)600         private void ensureCapacity(int size) {
601             if (fData == null) {
602                 fData = new boolean[32];
603             }
604             else if (fData.length <= size) {
605                 boolean[] newdata = new boolean[fData.length * 2];
606                 System.arraycopy(fData, 0, newdata, 0, fData.length);
607                 fData = newdata;
608             }
609         }
610     }
611 }
612