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
22package com.sun.org.apache.xerces.internal.impl.xs.opti;
23
24import java.util.ArrayList;
25import java.util.Enumeration;
26
27import com.sun.org.apache.xerces.internal.util.XMLSymbols;
28import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
29import com.sun.org.apache.xerces.internal.xni.QName;
30import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
31import com.sun.org.apache.xerces.internal.xni.XMLString;
32import org.w3c.dom.Attr;
33import org.w3c.dom.DOMImplementation;
34import org.w3c.dom.Element;
35import org.w3c.dom.NamedNodeMap;
36import org.w3c.dom.Node;
37
38/**
39 * @xerces.internal
40 *
41 * @author Rahul Srivastava, Sun Microsystems Inc.
42 * @author Sandy Gao, IBM
43 *
44 */
45public class SchemaDOM extends DefaultDocument {
46
47    static final int relationsRowResizeFactor = 15;
48    static final int relationsColResizeFactor = 10;
49
50    NodeImpl[][] relations;
51    // parent must be an element in this scheme
52    ElementImpl parent;
53    int currLoc;
54    int nextFreeLoc;
55    boolean hidden;
56    boolean inCDATA;
57
58    // for annotation support:
59    private StringBuffer fAnnotationBuffer = null;
60
61    public SchemaDOM() {
62        reset();
63    }
64
65
66    public ElementImpl startElement(QName element, XMLAttributes attributes,
67            int line, int column, int offset) {
68        ElementImpl node = new ElementImpl(line, column, offset);
69        processElement(element, attributes, node);
70        // now the current node added, becomes the parent
71        parent = node;
72        return node;
73    }
74
75    public ElementImpl emptyElement(QName element, XMLAttributes attributes,
76            int line, int column, int offset) {
77        ElementImpl node = new ElementImpl(line, column, offset);
78        processElement(element, attributes, node);
79        return node;
80    }
81
82    public ElementImpl startElement(QName element, XMLAttributes attributes,
83            int line, int column) {
84        return startElement(element, attributes, line, column, -1);
85    }
86
87    public ElementImpl emptyElement(QName element, XMLAttributes attributes,
88            int line, int column) {
89        return emptyElement(element, attributes, line, column, -1);
90    }
91
92    private void processElement(QName element, XMLAttributes attributes, ElementImpl node) {
93
94        // populate node
95        node.prefix = element.prefix;
96        node.localpart = element.localpart;
97        node.rawname = element.rawname;
98        node.uri = element.uri;
99        node.schemaDOM = this;
100
101        // set the attributes
102        Attr[] attrs = new Attr[attributes.getLength()];
103        for (int i=0; i<attributes.getLength(); i++) {
104            attrs[i] = new AttrImpl(node,
105                    attributes.getPrefix(i),
106                    attributes.getLocalName(i),
107                    attributes.getQName(i),
108                    attributes.getURI(i),
109                    attributes.getValue(i));
110        }
111        node.attrs = attrs;
112
113        // check if array needs to be resized
114        if (nextFreeLoc == relations.length) {
115            resizeRelations();
116        }
117
118        // store the current parent
119        //if (relations[currLoc][0] == null || relations[currLoc][0] != parent) {
120        if (relations[currLoc][0] != parent) {
121            relations[nextFreeLoc][0] = parent;
122            currLoc = nextFreeLoc++;
123        }
124
125        // add the current node as child of parent
126        boolean foundPlace = false;
127        int i = 1;
128        for (i = 1; i<relations[currLoc].length; i++) {
129            if (relations[currLoc][i] == null) {
130                foundPlace = true;
131                break;
132            }
133        }
134
135        if (!foundPlace) {
136            resizeRelations(currLoc);
137        }
138        relations[currLoc][i] = node;
139
140        parent.parentRow = currLoc;
141        node.row = currLoc;
142        node.col = i;
143    }
144
145
146    public void endElement()  {
147        // the parent of current parent node becomes the parent
148        // for the next node.
149        currLoc = parent.row;
150        parent = (ElementImpl)relations[currLoc][0];
151    }
152
153    // note that this will only be called within appinfo/documentation
154    void comment(XMLString text) {
155        fAnnotationBuffer.append("<!--");
156        if (text.length > 0) {
157            fAnnotationBuffer.append(text.ch, text.offset, text.length);
158        }
159        fAnnotationBuffer.append("-->");
160    }
161
162    // note that this will only be called within appinfo/documentation
163    void processingInstruction(String target, XMLString data) {
164        fAnnotationBuffer.append("<?").append(target);
165        if (data.length > 0) {
166            fAnnotationBuffer.append(' ').append(data.ch, data.offset, data.length);
167        }
168        fAnnotationBuffer.append("?>");
169    }
170
171    // note that this will only be called within appinfo/documentation
172    void characters(XMLString text) {
173
174        // escape characters if necessary
175        if (!inCDATA) {
176            final StringBuffer annotationBuffer = fAnnotationBuffer;
177            for (int i = text.offset; i < text.offset+text.length; ++i) {
178                char ch = text.ch[i];
179                if (ch == '&') {
180                    annotationBuffer.append("&amp;");
181                }
182                else if (ch == '<') {
183                    annotationBuffer.append("&lt;");
184                }
185                // character sequence "]]>" cannot appear in content,
186                // therefore we should escape '>'.
187                else if (ch == '>') {
188                    annotationBuffer.append("&gt;");
189                }
190                // If CR is part of the document's content, it
191                // must not be printed as a literal otherwise
192                // it would be normalized to LF when the document
193                // is reparsed.
194                else if (ch == '\r') {
195                    annotationBuffer.append("&#xD;");
196                }
197                else {
198                    annotationBuffer.append(ch);
199                }
200            }
201        }
202        else {
203            fAnnotationBuffer.append(text.ch, text.offset, text.length);
204        }
205    }
206
207    // note that this will only be called within appinfo/documentation
208    void charactersRaw(String text) {
209        fAnnotationBuffer.append(text);
210    }
211
212    void endAnnotation(QName elemName, ElementImpl annotation) {
213        fAnnotationBuffer.append("\n</").append(elemName.rawname).append(">");
214        annotation.fAnnotation = fAnnotationBuffer.toString();
215        // apparently, there is no sensible way of resetting these things
216        fAnnotationBuffer = null;
217    }
218
219    void endAnnotationElement(QName elemName) {
220        endAnnotationElement(elemName.rawname);
221    }
222
223    void endAnnotationElement(String elemRawName) {
224        fAnnotationBuffer.append("</").append(elemRawName).append(">");
225    }
226
227    void endSyntheticAnnotationElement(QName elemName, boolean complete) {
228        endSyntheticAnnotationElement(elemName.rawname, complete);
229    }
230
231    void endSyntheticAnnotationElement(String elemRawName, boolean complete) {
232        if(complete) {
233            fAnnotationBuffer.append("\n</").append(elemRawName).append(">");
234            // note that this is always called after endElement on <annotation>'s
235            // child and before endElement on annotation.
236            // hence, we must make this the child of the current
237            // parent's only child.
238            parent.fSyntheticAnnotation = fAnnotationBuffer.toString();
239
240            // apparently, there is no sensible way of resetting
241            // these things
242            fAnnotationBuffer = null;
243        } else      //capturing character calls
244            fAnnotationBuffer.append("</").append(elemRawName).append(">");
245    }
246
247    void startAnnotationCDATA() {
248        inCDATA = true;
249        fAnnotationBuffer.append("<![CDATA[");
250    }
251
252    void endAnnotationCDATA() {
253        fAnnotationBuffer.append("]]>");
254        inCDATA = false;
255    }
256
257    private void resizeRelations() {
258        NodeImpl[][] temp = new NodeImpl[relations.length+relationsRowResizeFactor][];
259        System.arraycopy(relations, 0, temp, 0, relations.length);
260        for (int i = relations.length ; i < temp.length ; i++) {
261            temp[i] = new NodeImpl[relationsColResizeFactor];
262        }
263        relations = temp;
264    }
265
266    private void resizeRelations(int i) {
267        NodeImpl[] temp = new NodeImpl[relations[i].length+relationsColResizeFactor];
268        System.arraycopy(relations[i], 0, temp, 0, relations[i].length);
269        relations[i] = temp;
270    }
271
272
273    public void reset() {
274
275        // help out the garbage collector
276        if(relations != null)
277            for(int i=0; i<relations.length; i++)
278                for(int j=0; j<relations[i].length; j++)
279                    relations[i][j] = null;
280        relations = new NodeImpl[relationsRowResizeFactor][];
281        parent = new ElementImpl(0, 0, 0);
282        parent.rawname = "DOCUMENT_NODE";
283        currLoc = 0;
284        nextFreeLoc = 1;
285        inCDATA = false;
286        for (int i=0; i<relationsRowResizeFactor; i++) {
287            relations[i] = new NodeImpl[relationsColResizeFactor];
288        }
289        relations[currLoc][0] = parent;
290    }
291
292
293    public void printDOM() {
294        /*
295         for (int i=0; i<relations.length; i++) {
296         if (relations[i][0] != null) {
297         for (int j=0; j<relations[i].length; j++) {
298         if (relations[i][j] != null) {
299         System.out.print(relations[i][j].nodeType+"-"+relations[i][j].parentRow+"  ");
300         }
301         }
302         System.out.println("");
303         }
304         }
305         */
306        //traverse(getDocumentElement(), 0);
307    }
308
309
310    // debug methods
311
312    public static void traverse(Node node, int depth) {
313        indent(depth);
314        System.out.print("<"+node.getNodeName());
315
316        if (node.hasAttributes()) {
317            NamedNodeMap attrs = node.getAttributes();
318            for (int i=0; i<attrs.getLength(); i++) {
319                System.out.print("  "+((Attr)attrs.item(i)).getName()+"=\""+((Attr)attrs.item(i)).getValue()+"\"");
320            }
321        }
322
323        if (node.hasChildNodes()) {
324            System.out.println(">");
325            depth+=4;
326            for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
327                traverse(child, depth);
328            }
329            depth-=4;
330            indent(depth);
331            System.out.println("</"+node.getNodeName()+">");
332        }
333        else {
334            System.out.println("/>");
335        }
336    }
337
338    public static void indent(int amount) {
339        for (int i = 0; i < amount; i++) {
340            System.out.print(' ');
341        }
342    }
343
344    // org.w3c.dom methods
345    public Element getDocumentElement() {
346        // this returns a parent node, known to be an ElementImpl
347        return (ElementImpl)relations[0][1];
348    }
349
350    public DOMImplementation getImplementation() {
351        return SchemaDOMImplementation.getDOMImplementation();
352    }
353
354    // commence the serialization of an annotation
355    void startAnnotation(QName elemName, XMLAttributes attributes,
356            NamespaceContext namespaceContext) {
357        startAnnotation(elemName.rawname, attributes, namespaceContext);
358    }
359    void startAnnotation(String elemRawName, XMLAttributes attributes,
360            NamespaceContext namespaceContext) {
361        if(fAnnotationBuffer == null) fAnnotationBuffer = new StringBuffer(256);
362        fAnnotationBuffer.append("<").append(elemRawName).append(" ");
363
364        // attributes are a bit of a pain.  To get this right, we have to keep track
365        // of the namespaces we've seen declared, then examine the namespace context
366        // for other namespaces so that we can also include them.
367        // optimized for simplicity and the case that not many
368        // namespaces are declared on this annotation...
369        ArrayList namespaces = new ArrayList();
370        for (int i = 0; i < attributes.getLength(); ++i) {
371            String aValue = attributes.getValue(i);
372            String aPrefix = attributes.getPrefix(i);
373            String aQName = attributes.getQName(i);
374            // if it's xmlns:* or xmlns, must be a namespace decl
375            if (aPrefix == XMLSymbols.PREFIX_XMLNS || aQName == XMLSymbols.PREFIX_XMLNS) {
376                namespaces.add(aPrefix == XMLSymbols.PREFIX_XMLNS ?
377                        attributes.getLocalName(i) : XMLSymbols.EMPTY_STRING);
378            }
379            fAnnotationBuffer.append(aQName).append("=\"").append(processAttValue(aValue)).append("\" ");
380        }
381        // now we have to look through currently in-scope namespaces to see what
382        // wasn't declared here
383        Enumeration currPrefixes = namespaceContext.getAllPrefixes();
384        while(currPrefixes.hasMoreElements()) {
385            String prefix = (String)currPrefixes.nextElement();
386            String uri = namespaceContext.getURI(prefix);
387            if (uri == null) {
388                uri = XMLSymbols.EMPTY_STRING;
389            }
390            if (!namespaces.contains(prefix)) {
391                // have to declare this one
392                if(prefix == XMLSymbols.EMPTY_STRING) {
393                    fAnnotationBuffer.append("xmlns").append("=\"").append(processAttValue(uri)).append("\" ");
394                }
395                else {
396                    fAnnotationBuffer.append("xmlns:").append(prefix).append("=\"").append(processAttValue(uri)).append("\" ");
397                }
398            }
399        }
400        fAnnotationBuffer.append(">\n");
401    }
402    void startAnnotationElement(QName elemName, XMLAttributes attributes) {
403        startAnnotationElement(elemName.rawname, attributes);
404    }
405    void startAnnotationElement(String elemRawName, XMLAttributes attributes) {
406        fAnnotationBuffer.append("<").append(elemRawName);
407        for(int i=0; i<attributes.getLength(); i++) {
408            String aValue = attributes.getValue(i);
409            fAnnotationBuffer.append(" ").append(attributes.getQName(i)).append("=\"").append(processAttValue(aValue)).append("\"");
410        }
411        fAnnotationBuffer.append(">");
412    }
413
414    private static String processAttValue(String original) {
415        final int length = original.length();
416        // normally, nothing will happen
417        for (int i = 0; i < length; ++i) {
418            char currChar = original.charAt(i);
419            if (currChar == '"' || currChar == '<' || currChar == '&' ||
420                    currChar == 0x09 || currChar == 0x0A || currChar == 0x0D) {
421                return escapeAttValue(original, i);
422            }
423        }
424        return original;
425    }
426
427    private static String escapeAttValue(String original, int from) {
428        int i;
429        final int length = original.length();
430        StringBuffer newVal = new StringBuffer(length);
431        newVal.append(original.substring(0, from));
432        for (i = from; i < length; ++i) {
433            char currChar = original.charAt(i);
434            if (currChar == '"') {
435                newVal.append("&quot;");
436            }
437            else if (currChar == '<') {
438                newVal.append("&lt;");
439            }
440            else if (currChar == '&') {
441                newVal.append("&amp;");
442            }
443            // Must escape 0x09, 0x0A and 0x0D if they appear in attribute
444            // value so that they may be round-tripped. They would otherwise
445            // be transformed to a 0x20 during attribute value normalization.
446            else if (currChar == 0x09) {
447                newVal.append("&#x9;");
448            }
449            else if (currChar == 0x0A) {
450                newVal.append("&#xA;");
451            }
452            else if (currChar == 0x0D) {
453                newVal.append("&#xD;");
454            }
455            else {
456                newVal.append(currChar);
457            }
458        }
459        return newVal.toString();
460    }
461}
462