~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to schema2beans/rt/src/org/netbeans/modules/schema2beans/XMLUtil.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.modules.schema2beans;
 
43
 
 
44
import java.io.*;
 
45
import org.w3c.dom.*;
 
46
import org.xml.sax.*;
 
47
 
 
48
public class XMLUtil {
 
49
    private XMLUtil() {}
 
50
 
 
51
    /**
 
52
     * Takes some text to be printed into an XML stream and escapes any
 
53
     * characters that might make it invalid XML (like '<').
 
54
     */
 
55
    public static void printXML(StringBuffer out, String msg) {
 
56
        printXML(out, msg, true);
 
57
    }
 
58
 
 
59
    public static void printXML(StringBuffer out, String msg, boolean attribute) {
 
60
        if (msg == null) return;
 
61
        appendLine(out, msg, attribute);
 
62
    }
 
63
 
 
64
    /** @deprecated this public method is not expected to be used by schema2beans client 
 
65
     */
 
66
    public static void printXML(StringBuffer out, char msg, boolean attribute) {
 
67
        if (msg == '&')
 
68
            out.append("&amp;");
 
69
        else if (msg == '<')
 
70
            out.append("&lt;");
 
71
        else if (msg == '>')
 
72
            out.append("&gt;");
 
73
        else if (attribute) {
 
74
            if (msg == '"')
 
75
                out.append("&quot;");
 
76
            else if (msg == '\'')
 
77
                out.append("&apos;");
 
78
            else if (msg == '\n')
 
79
                out.append("&#xA");
 
80
            else if (msg == '\t')
 
81
                out.append("&#x9");
 
82
            else
 
83
                out.append(msg);
 
84
        } else
 
85
            out.append(msg);
 
86
    }
 
87
    
 
88
        /**
 
89
         * Takes some text to be printed into an XML stream and escapes any
 
90
         * characters that might make it invalid XML (like '<').
 
91
         */
 
92
        public static void writeXML(java.io.Writer out, String msg) throws java.io.IOException {
 
93
                writeXML(out, msg, true);
 
94
        }
 
95
 
 
96
        public static void writeXML(java.io.Writer out, String msg, boolean attribute) throws java.io.IOException {
 
97
            if (msg == null) return;
 
98
            appendLine(out,msg,attribute);
 
99
        }
 
100
        
 
101
        /** @deprecated this public method is not expected to be used by schema2beans client 
 
102
         */ 
 
103
        public static void writeXML(java.io.Writer out, char msg, boolean attribute) throws java.io.IOException {
 
104
                if (msg == '&')
 
105
                        out.write("&amp;");
 
106
                else if (msg == '<')
 
107
                        out.write("&lt;");
 
108
                else if (msg == '>')
 
109
                        out.write("&gt;");
 
110
                else if (attribute) {
 
111
            if (msg == '"')
 
112
                out.write("&quot;");
 
113
            else if (msg == '\'')
 
114
                out.write("&apos;");
 
115
            else if (msg == '\n')
 
116
                out.write("&#xA;");
 
117
            else if (msg == '\t')
 
118
                out.write("&#x9;");
 
119
            else
 
120
                out.write(msg);
 
121
        } else
 
122
                        out.write(msg);
 
123
        }
 
124
 
 
125
    public static boolean shouldEscape(char c) {
 
126
        if (c == '&')
 
127
            return true;
 
128
        else if (c == '<')
 
129
            return true;
 
130
        else if (c == '>')
 
131
            return true;
 
132
        return false;
 
133
    }
 
134
 
 
135
    public static boolean shouldEscape(String s) {
 
136
        if (s == null)
 
137
            return false;
 
138
        int msgLength = s.length();
 
139
        for (int i = 0; i < msgLength; ++i) {
 
140
            char c = s.charAt(i);
 
141
            if (shouldEscape(c))
 
142
                return true;
 
143
        }
 
144
        return false;
 
145
    }
 
146
 
 
147
    /**
 
148
     * Takes some text to be printed into an XML stream and escapes any
 
149
     * characters that might make it invalid XML (like '<').
 
150
     */
 
151
    public static void printXML(java.io.Writer out, String msg) throws java.io.IOException {
 
152
        printXML(out, msg, true);
 
153
    }
 
154
 
 
155
    public static void printXML(java.io.Writer out, String msg, boolean attribute) throws java.io.IOException {
 
156
        if (msg == null) return;
 
157
        appendLine(out,msg,attribute);
 
158
    }
 
159
    
 
160
    private static void appendLine(StringBuffer out, String msg, boolean attribute) {
 
161
        out.append(convertChars(msg,attribute));
 
162
    }
 
163
    
 
164
    private static void appendLine(java.io.Writer out, String msg, boolean attribute) throws java.io.IOException  {
 
165
        out.write(convertChars(msg,attribute));
 
166
    }
 
167
 
 
168
    private static String convertChars(String msg, boolean attribute) {
 
169
        String result=msg;
 
170
        if (msg.indexOf("&")>=0) //NOI18N
 
171
            result = result.replaceAll("&","&amp;"); //NOI18N
 
172
        if (msg.indexOf("<")>=0) //NOI18N
 
173
            result = result.replaceAll("<","&lt;"); //NOI18N
 
174
        if (msg.indexOf(">")>=0) //NOI18N   
 
175
            result = result.replaceAll(">","&gt;"); //NOI18N
 
176
        if (attribute) { //NOI18N
 
177
            if (msg.indexOf("\"")>=0) //NOI18N
 
178
                result = result.replaceAll("\"","&quot;"); //NOI18N
 
179
            if (msg.indexOf("'")>=0) //NOI18N
 
180
                result = result.replaceAll("'","&apos;"); //NOI18N
 
181
            if (msg.indexOf("\n")>=0) //NOI18N
 
182
                result = result.replaceAll("\n","&#xA"); //NOI18N
 
183
            if (msg.indexOf("\t")>=0) //NOI18N
 
184
                result = result.replaceAll("\t","&#x9"); //NOI18N
 
185
        }
 
186
        return result;
 
187
    }
 
188
    
 
189
    /** @deprecated this public method is not expected to be used by schema2beans client 
 
190
     */
 
191
    public static void printXML(java.io.Writer out, char msg, boolean attribute) throws java.io.IOException {
 
192
        if (msg == '&')
 
193
            out.write("&amp;");
 
194
        else if (msg == '<')
 
195
            out.write("&lt;");
 
196
        else if (msg == '>')
 
197
            out.write("&gt;");
 
198
        else if (attribute) {
 
199
            if (msg == '"')
 
200
                out.write("&quot;");
 
201
            else if (msg == '\'')
 
202
                out.write("&apos;");
 
203
            else if (msg == '\n')
 
204
                out.write("&#xA;");
 
205
            else if (msg == '\t')
 
206
                out.write("&#x9;");
 
207
            else
 
208
                out.write(msg);
 
209
        }
 
210
        else
 
211
            out.write(msg);
 
212
    }
 
213
 
 
214
    public static class DOMWriter {
 
215
        private java.io.Writer out;
 
216
        private boolean writeCData = false;
 
217
        private String docTypePublic;
 
218
        private String docTypeSystem;
 
219
        
 
220
        public DOMWriter() {
 
221
        }
 
222
 
 
223
        public void setWriter(java.io.Writer out) {
 
224
            this.out = out;
 
225
        }
 
226
        
 
227
        public void setWriteCData(boolean value) {
 
228
            writeCData = value;
 
229
        }
 
230
 
 
231
        public void setDocTypePublic(String value) {
 
232
            docTypePublic = value;
 
233
        }
 
234
 
 
235
        public void setDocTypeSystem(String value) {
 
236
            docTypeSystem = value;
 
237
        }
 
238
 
 
239
        /**
 
240
         * Same as write(OutputStream os, Document document)
 
241
         * where os = the file's OutputStream.
 
242
         */
 
243
        public void write(File f, Document document) throws java.io.IOException {
 
244
            OutputStream fout = new FileOutputStream(f);
 
245
            try {
 
246
                write(fout, document);
 
247
            } finally {
 
248
                fout.close();
 
249
            }
 
250
        }
 
251
 
 
252
        /**
 
253
         * Same as write(OutputStream os, String encoding, Document document)
 
254
         * where encoding == null.
 
255
         */
 
256
        public void write(OutputStream os, Document document) throws java.io.IOException {
 
257
            write(os, null, document);
 
258
        }
 
259
 
 
260
        /**
 
261
         * Create an output Writer based on the OutputStream using the
 
262
         * encoding (use "UTF-8" if encoding == null), then write the DOM
 
263
         * graph out.
 
264
         */
 
265
        public void write(OutputStream os, String encoding, Document document) throws java.io.IOException {
 
266
            if (encoding == null)
 
267
                encoding = "UTF-8";
 
268
            out = new BufferedWriter(new OutputStreamWriter(os, encoding));
 
269
            write(document, encoding);
 
270
        }
 
271
 
 
272
        /**
 
273
         * Assumes that the output Writer has already been set.
 
274
         */
 
275
        public void write(Document document) throws java.io.IOException {
 
276
            write(document, null);
 
277
        }
 
278
        
 
279
        /**
 
280
         * Assumes that the output Writer has already been set.
 
281
         * @param encoding goes into the XML header.
 
282
         */
 
283
        public void write(Document document, String encoding) throws java.io.IOException {
 
284
            write(document, encoding, true);
 
285
        }
 
286
        
 
287
        /**
 
288
         * Assumes that the output Writer has already been set.
 
289
         * @param encoding goes into the XML header.
 
290
         * @param writeHeader whether or not the "<?xml ..." header gets
 
291
         *                    written out as well.
 
292
         */
 
293
        public void write(Document document, String encoding,
 
294
                          boolean writeHeader) throws java.io.IOException {
 
295
            if (writeHeader) {
 
296
                out.write("<?xml version=\"1.0\"");    // NOI18N
 
297
                if (encoding != null) {
 
298
                    out.write(" encoding=\""+encoding+"\"?>\n");    // NOI18N
 
299
                } else
 
300
                    out.write(" encoding=\"UTF-8\"?>\n");    // NOI18N
 
301
            }
 
302
            if (docTypePublic != null || docTypeSystem != null) {
 
303
                String docName = getDocTypeName(document);
 
304
                DocumentType docType = document.getDoctype();
 
305
                NamedNodeMap entities = null;
 
306
                if (docType != null)
 
307
                    entities = docType.getEntities();
 
308
                write(docName, docTypePublic, docTypeSystem, entities);
 
309
                out.write("\n");
 
310
            }
 
311
            NodeList children = document.getChildNodes();
 
312
            int length = children.getLength();
 
313
            // First print out any DocumentTypes
 
314
            for (int i = 0; i < length; ++i) {
 
315
                Node node = children.item(i);
 
316
                if (node instanceof DocumentType) {
 
317
                    write(node);
 
318
                    out.write("\n");
 
319
                }
 
320
            }
 
321
            // Now print everything, but DocumentTypes
 
322
            for (int i = 0; i < length; ++i) {
 
323
                Node node = children.item(i);
 
324
                if (!(node instanceof DocumentType)) {
 
325
                    write(node);
 
326
                    out.write("\n");
 
327
                }
 
328
            }
 
329
 
 
330
            out.flush();
 
331
        }
 
332
    
 
333
       public void write(Node node) throws java.io.IOException {
 
334
            boolean needsReturnBetweenChildren = false;
 
335
 
 
336
            NodeList children = node.getChildNodes();
 
337
            if (node instanceof Element) {
 
338
                out.write("<"+node.getNodeName());
 
339
                write(node.getAttributes());
 
340
                if (children.getLength() == 0 ||
 
341
                    (children.getLength() == 1 &&
 
342
                     children.item(0) instanceof Text &&
 
343
                     "".equals(children.item(0).getNodeValue()) )) {
 
344
                    out.write("/>");
 
345
                    return;
 
346
                }
 
347
                out.write(">");
 
348
            } else if (node instanceof Text) {
 
349
                printXML(node.getNodeValue(), false);
 
350
            } else if (node instanceof Document) {
 
351
                needsReturnBetweenChildren = true;
 
352
            } else if (node instanceof DocumentType) {
 
353
                write((DocumentType) node);
 
354
            } else if (node instanceof Comment) {
 
355
                write((Comment) node);
 
356
            } else if (node instanceof Entity) {
 
357
                write((Entity) node);
 
358
            } else if (node instanceof ProcessingInstruction) {
 
359
                write((ProcessingInstruction) node);
 
360
            } else {
 
361
                System.err.println("! schema2beans found unknown node type in DOM graph:");
 
362
                System.err.println("write: node.getClass="+node.getClass()+" node="+node);
 
363
                System.err.println("write: nodename="+node.getNodeName()+" nodevalue="+node.getNodeValue());
 
364
                System.err.println("write: getAttributes="+node.getAttributes());
 
365
            }
 
366
        
 
367
            int length = children.getLength();
 
368
            for (int i = 0; i < length; ++i) {
 
369
                write(children.item(i));
 
370
                if (needsReturnBetweenChildren)
 
371
                    out.write("\n");
 
372
            }
 
373
            if (node instanceof Element) {
 
374
                out.write("</"+node.getNodeName()+">");
 
375
            }
 
376
        }
 
377
 
 
378
        protected void write(DocumentType docType) throws java.io.IOException {
 
379
            //System.out.println("! FOUND DOCTYPE for "+docType.getName());
 
380
            if (docTypePublic != null || docTypeSystem != null) {
 
381
                // The header printing has already taken care of the DOCTYPE.
 
382
                return;
 
383
            }
 
384
            write(docType.getName(), docType.getPublicId(),
 
385
                  docType.getSystemId(), docType.getEntities());
 
386
        }
 
387
 
 
388
        protected void write(String docName, String publicId,
 
389
                             String systemId, NamedNodeMap entities) throws java.io.IOException {
 
390
            out.write("<!DOCTYPE "+docName);    // NOI18N
 
391
            if (publicId != null) {
 
392
                out.write(" PUBLIC \"");        // NOI18N
 
393
                XMLUtil.printXML(out, publicId);
 
394
                out.write("\"");        // NOI18N
 
395
                if (systemId == null)
 
396
                    systemId = "SYSTEM";        // NOI18N
 
397
            }
 
398
            if (systemId != null) {
 
399
                out.write(" \"");       // NOI18N
 
400
                XMLUtil.printXML(out, systemId);
 
401
                out.write("\"");        // NOI18N
 
402
            }
 
403
            if (entities != null) {
 
404
                int length = entities.getLength();
 
405
                if (length > 0) {
 
406
                    out.write(" [");    // NOI18N
 
407
                    for (int i = 0; i < length; ++i) {
 
408
                        Node node = entities.item(i);
 
409
                        write(node);
 
410
                    }
 
411
                    out.write("]");     // NOI18N
 
412
                }
 
413
            }
 
414
            out.write(">");     // NOI18N
 
415
        }
 
416
 
 
417
        protected void write(Comment comment) throws java.io.IOException {
 
418
            // Does not need to have anything escaped (no printXML).
 
419
            out.write("<!--");
 
420
            String text = comment.getNodeValue();
 
421
            // A comment is not allow to have "--" inside of it.
 
422
            int pos = text.indexOf("--");
 
423
            while (pos >= 0) {
 
424
                out.write(text.substring(0, pos));
 
425
                out.write("&#x2d;&#x2d;");
 
426
                text = text.substring(pos+2, text.length());
 
427
                pos = text.indexOf("--");
 
428
            }
 
429
            out.write(text);
 
430
            out.write("-->");
 
431
        }
 
432
 
 
433
        protected void write(Entity entity) throws java.io.IOException {
 
434
            out.write("<!ENTITY "+entity.getNodeName());
 
435
            /*
 
436
              We don't seem to be able to get any useful info out of the
 
437
              Entity object.
 
438
          
 
439
              out.write(" notation ");
 
440
              if (entity.getNotationName() != null)
 
441
              out.write(entity.getNotationName());
 
442
              out.write(" publicid ");
 
443
              if (entity.getPublicId() != null)
 
444
              out.write(entity.getPublicId());
 
445
              out.write(" systemid ");
 
446
              if (entity.getSystemId() != null)
 
447
              out.write(entity.getSystemId());
 
448
            */
 
449
            out.write(" UNKNOWN>");
 
450
        }
 
451
 
 
452
        protected void write(ProcessingInstruction pi) throws java.io.IOException {
 
453
            // Does not need to have anything escaped (no printXML).
 
454
            if ("xml".equals(pi.getTarget())) {
 
455
                // We've already printed out the standard xml PI, suppress this one.
 
456
                return;
 
457
            }
 
458
            out.write("<?"+pi.getTarget()+" "+pi.getData()+"?>");
 
459
        }
 
460
 
 
461
        /**
 
462
         * This is used to print attributes.
 
463
         */
 
464
        protected void write(NamedNodeMap nodes) throws java.io.IOException {
 
465
            int length = nodes.getLength();
 
466
            for (int i = 0; i < length; ++i) {
 
467
                Node node = nodes.item(i);
 
468
                out.write(" ");
 
469
                out.write(node.getNodeName());
 
470
                out.write("=\"");
 
471
                XMLUtil.printXML(out, node.getNodeValue());
 
472
                out.write("\"");
 
473
            }
 
474
        }
 
475
 
 
476
        protected void printXML(String msg, boolean attribute) throws java.io.IOException {
 
477
            if (writeCData && msg.indexOf("]]>") < 0) {
 
478
                boolean shouldEscape = XMLUtil.shouldEscape(msg);
 
479
                if (shouldEscape)
 
480
                    out.write("<![CDATA[");
 
481
                out.write(msg);
 
482
                if (shouldEscape)
 
483
                    out.write("]]>");
 
484
            } else
 
485
                XMLUtil.printXML(out, msg, attribute);
 
486
        }
 
487
    }
 
488
 
 
489
    // Given @param doc what should it's DOCTYPE name be.
 
490
    static protected String getDocTypeName(Document doc) {
 
491
        // First look for a DOCTYPE
 
492
        NodeList children = doc.getChildNodes();
 
493
        int length = children.getLength();
 
494
        for (int i = 0; i < length; ++i) {
 
495
            Node node = children.item(i);
 
496
            if (node instanceof DocumentType) {
 
497
                DocumentType docType = (DocumentType) node;
 
498
                return docType.getName();
 
499
            }
 
500
        }
 
501
        // Otherwise, check the first node of the actual document
 
502
        Node rootNode = doc.getDocumentElement();
 
503
        return rootNode.getNodeName();
 
504
    }
 
505
 
 
506
    /**
 
507
     * Reformat the DOM graph to make it look like pretty XML.
 
508
     *
 
509
     * @param doc The Document to create new TextNodes from.
 
510
     * @param indent The String used to indent per level
 
511
     */
 
512
    public static void reindent(Document doc, String indent) {
 
513
        reindent(doc, doc, -1, indent);
 
514
    }
 
515
    
 
516
    /**
 
517
     * Reformat the DOM graph to make it look like pretty XML.
 
518
     *
 
519
     * @param doc The Document to create new TextNodes from.
 
520
     * @param node The top of the tree to reindent from.
 
521
     * @param indent The String used to indent per level
 
522
     * @param level How far in to reindent
 
523
     * @return true if node is a Text node that has only whitespace
 
524
     */
 
525
    public static boolean reindent(Document doc, Node node,
 
526
                                   int level, String indent) {
 
527
        String nodeValue = node.getNodeValue();
 
528
 
 
529
        boolean hasOnlyWhitespaceTextChildren = true;
 
530
        NodeList children = node.getChildNodes();
 
531
        int length = children.getLength();
 
532
        for (int i = 0; i < length; ++i) {
 
533
            if (!reindent(doc, children.item(i), level+1, indent))
 
534
                hasOnlyWhitespaceTextChildren = false;
 
535
        }
 
536
 
 
537
        /*
 
538
        try {
 
539
            printLevel(System.out, level, indent,
 
540
                       node.getNodeName()+": \""+nodeValue+"\"\n");
 
541
            printLevel(System.out, level, indent,
 
542
                       "hasOnlyWhitespaceTextChildren="+hasOnlyWhitespaceTextChildren+"\n");
 
543
        } catch (java.io.IOException e) {
 
544
            e.printStackTrace();
 
545
        }
 
546
        */
 
547
 
 
548
        if (hasOnlyWhitespaceTextChildren && level >= 0  && length > 0) {
 
549
            // We can reindent this one.  So, go thru each child node
 
550
            // and make sure it's intendation is where we want it.
 
551
            
 
552
            StringBuffer idealWhitespaceBuf = new StringBuffer();
 
553
            printLevel(idealWhitespaceBuf, level, indent);
 
554
            String idealFinalWhitespace = "\n" + idealWhitespaceBuf.toString().intern();
 
555
            printLevel(idealWhitespaceBuf, 1, indent);
 
556
            String idealChildWhitespace = "\n"+idealWhitespaceBuf.toString().intern();
 
557
            //System.out.println("idealChildWhitespace='"+idealChildWhitespace+"'");
 
558
            //
 
559
            // Check to make sure the last child node is a text node.
 
560
            // If not, insert the correct spacing at the end.
 
561
            //
 
562
            if (length > 1 && !(children.item(length-1) instanceof Text)) {
 
563
                //System.out.println("Inserting additional whitespace at end of child list.");
 
564
                node.appendChild(doc.createTextNode(idealFinalWhitespace));
 
565
                ++length;
 
566
            }
 
567
            //System.out.println("node.getNodeName="+node.getNodeName()+" children.length="+length);
 
568
            
 
569
            boolean shouldBeTextNode = true;  // This alternates
 
570
            Text textNode;
 
571
            for (int i = 0; i < length; ++i) {
 
572
                Node childNode = children.item(i);
 
573
                boolean isTextNode = (childNode instanceof Text);
 
574
                //System.out.println("shouldBeTextNode="+shouldBeTextNode+" isTextNode="+isTextNode+" "+childNode.getNodeName());
 
575
                if (shouldBeTextNode) {
 
576
                    if (isTextNode) {
 
577
                        String childNodeValue = childNode.getNodeValue().intern();
 
578
                        if (length == 1) {
 
579
                            // We have a single text child, don't mess with
 
580
                            // it's contents.
 
581
                            continue;
 
582
                        }
 
583
                        
 
584
                        textNode = (Text) childNode;
 
585
                        // Need to make sure it has the correct whitespace
 
586
                        if (i == length-1) {
 
587
                            if (idealFinalWhitespace != childNodeValue) {
 
588
                                //System.out.println("!Incorrect whitespace on final!");
 
589
                                if (textNode.getLength() > 0)
 
590
                                    textNode.deleteData(0, textNode.getLength());
 
591
                                textNode.appendData(idealFinalWhitespace);
 
592
                            }
 
593
                            
 
594
                        } else {
 
595
                            if (idealChildWhitespace != childNodeValue) {
 
596
                                //System.out.println("!Incorrect whitespace: '"+childNodeValue+"' versus ideal of '"+idealChildWhitespace+"'");
 
597
                                textNode.deleteData(0, textNode.getLength());
 
598
                                textNode.appendData(idealChildWhitespace);
 
599
                            }
 
600
                        }
 
601
                        shouldBeTextNode ^= true;
 
602
                    } else {
 
603
                        // Need to insert a whitespace node
 
604
                        //System.out.println("Need to insert a whitespace node before "+childNode.getNodeName()+": "+childNode.getNodeValue());
 
605
                        if (i == length-1) {
 
606
                            //System.out.println("It's a final one!");
 
607
                            node.insertBefore(doc.createTextNode(idealChildWhitespace), childNode);
 
608
                            node.appendChild(doc.createTextNode(idealFinalWhitespace));
 
609
                            ++length;
 
610
                        } else {
 
611
                            //System.out.println("Not final.");
 
612
                            node.insertBefore(doc.createTextNode(idealChildWhitespace), childNode);
 
613
                        }
 
614
                        //
 
615
                        // We updated our list while going thru it at the same
 
616
                        // time, so update our indices to account for the
 
617
                        // new growth.
 
618
                        //
 
619
                        ++i;  
 
620
                        ++length;
 
621
                    }
 
622
                } else {
 
623
                    if (isTextNode) {
 
624
                        // The last whitespace node is correct, so this one
 
625
                        // must be extra.
 
626
                        //System.out.println("Extra unneeded whitespace");
 
627
                        node.removeChild(childNode);
 
628
                        --i;
 
629
                        --length;
 
630
                        if (i == length-1 && i >= 0) {
 
631
                            //System.out.println("It's a final one!");
 
632
                            // Go back and fix up the last node.
 
633
                            childNode = children.item(i);
 
634
                            String childNodeValue = childNode.getNodeValue().intern();
 
635
                            if (idealFinalWhitespace != childNodeValue) {
 
636
                                textNode = (Text) childNode;
 
637
                                //System.out.println("!Incorrect whitespace on final!");
 
638
                                if (textNode.getLength() > 0)
 
639
                                    textNode.deleteData(0, textNode.getLength());
 
640
                                textNode.appendData(idealFinalWhitespace);
 
641
                            }
 
642
                        }
 
643
                    } else {
 
644
                        // This is just right.
 
645
                        //System.out.println("This is just right.");
 
646
                        shouldBeTextNode ^= true;
 
647
                    }
 
648
                }
 
649
            }
 
650
        }
 
651
 
 
652
        // Let my caller know if I'm a Text node that has only whitespace
 
653
        // or not.
 
654
        if (node instanceof Text) {
 
655
            if (nodeValue == null)
 
656
                return true;
 
657
            return (nodeValue.trim().equals(""));
 
658
        }
 
659
        return true;
 
660
    }
 
661
 
 
662
    protected static void printLevel(StringBuffer out, int level, String indent) {
 
663
        for (int i = 0; i < level; ++i) {
 
664
            out.append(indent);
 
665
        }
 
666
    }
 
667
 
 
668
    /**
 
669
     * Given an XPath expression, find it's location in a Document
 
670
     * @return null if not found
 
671
     */
 
672
    public static Locator findLocationXPath(InputSource in, String xpathExpr) throws IOException, org.xml.sax.SAXException {
 
673
        XMLReader parser;
 
674
        try {
 
675
            javax.xml.parsers.SAXParserFactory spf
 
676
                = javax.xml.parsers.SAXParserFactory.newInstance();
 
677
            spf.setNamespaceAware(true);
 
678
            parser = spf.newSAXParser().getXMLReader();
 
679
        } catch (javax.xml.parsers.ParserConfigurationException e) {
 
680
            throw new RuntimeException(e);
 
681
        }
 
682
        XPathLocator locator = new XPathLocator(xpathExpr);
 
683
        parser.setContentHandler(locator);
 
684
        parser.parse(in);
 
685
        return locator.getDocumentLocator();
 
686
    }
 
687
    
 
688
    /** Test if character can be in attr value
 
689
     */
 
690
    public static boolean isAttrContent(int i) {
 
691
        // return false for leading ACSII chars (except tab char)
 
692
        if (i<9) return false;
 
693
        if (i>9 && i<32) return false;
 
694
        // return false for <, ]
 
695
        // if (i==60 || i==93) return false;
 
696
        // otherwise return true
 
697
        return true;
 
698
    }
 
699
 
 
700
    private static class XPathLocator
 
701
        extends org.xml.sax.helpers.DefaultHandler implements ContentHandler {
 
702
        private String xpathExpr;
 
703
        private String[] xpathParts;
 
704
        private int partNum;
 
705
        private String desiredElementName;
 
706
        private int desiredPosition;
 
707
        private boolean isAttribute;
 
708
        private Locator locator = null;
 
709
        private Locator resultLocator = null;
 
710
 
 
711
        public XPathLocator(String xpathExpr) {
 
712
            xpathExpr = xpathExpr.trim();
 
713
            if (xpathExpr.startsWith("/"))
 
714
                xpathExpr = xpathExpr.substring(1, xpathExpr.length());
 
715
            this.xpathExpr = xpathExpr;
 
716
            xpathParts = xpathExpr.split("/");  // This is a bit too simple.
 
717
            partNum = 0;
 
718
            setElementName();
 
719
        }
 
720
 
 
721
        private void setElementName() {
 
722
            desiredElementName = xpathParts[partNum].trim();
 
723
            desiredPosition = 0;
 
724
            isAttribute = false;
 
725
            int startPos = desiredElementName.indexOf('[');
 
726
            int endPos = desiredElementName.indexOf(']');
 
727
            //System.out.println("desiredElementName="+desiredElementName);
 
728
            if (startPos >= 0) {
 
729
                if (endPos < 0)
 
730
                    throw new IllegalArgumentException("XPath subexpression ("+desiredElementName+") is missing an ending ']'.");
 
731
                String subExpr = desiredElementName.substring(startPos+1,
 
732
                                                              endPos).trim();
 
733
                desiredElementName = desiredElementName.substring(0, startPos);
 
734
                //System.out.println("subExpr="+subExpr);
 
735
                if (subExpr.startsWith("position()=")) {
 
736
                    desiredPosition = Integer.parseInt(subExpr.substring(11, subExpr.length()));
 
737
                } else {
 
738
                    boolean allDigits = subExpr.length() > 0;
 
739
                    for (int i = 0; i < subExpr.length(); ++i) {
 
740
                        if (!Character.isDigit(subExpr.charAt(i))) {
 
741
                            allDigits = false;
 
742
                            break;
 
743
                        }
 
744
                    }
 
745
                    if (allDigits) {
 
746
                        desiredPosition = Integer.parseInt(subExpr);
 
747
                    } else {
 
748
                        throw new UnsupportedOperationException("XPath ("+subExpr+" in "+xpathExpr+") not supported.");
 
749
                    }
 
750
                }
 
751
            } else if (desiredElementName.startsWith("@")) {
 
752
                isAttribute = true;
 
753
                desiredElementName = desiredElementName.substring(1, desiredElementName.length());
 
754
            }
 
755
            //System.out.println("desiredElementName="+desiredElementName);
 
756
        }
 
757
 
 
758
        /**
 
759
         * @return true means done
 
760
         */
 
761
        private boolean foundGotoNext() {
 
762
            ++partNum;
 
763
            if (partNum >= xpathParts.length) {
 
764
                // Found the final one!
 
765
                resultLocator = new org.xml.sax.helpers.LocatorImpl(locator);
 
766
                return true;
 
767
            } else {
 
768
                // goto the next subexpression
 
769
                setElementName();
 
770
                return false;
 
771
            }
 
772
        }
 
773
 
 
774
        public Locator getDocumentLocator() {
 
775
            return resultLocator;
 
776
        }
 
777
        
 
778
        public void setDocumentLocator(Locator locator) {
 
779
            this.locator = locator;  
 
780
        }
 
781
 
 
782
        public void startElement(String namespaceURI, String localName,
 
783
                                 String rawName, Attributes attrs) throws SAXException {
 
784
            if (resultLocator != null) {
 
785
                // It's already found.
 
786
                return;
 
787
            }
 
788
            if (desiredElementName.equals(localName) ||
 
789
                desiredElementName.equals(rawName)) {
 
790
                //System.out.println("Found "+desiredElementName);
 
791
                if (desiredPosition == 0) {
 
792
                    // Found the one we wanted
 
793
                    if (!foundGotoNext()) {
 
794
                        // See if the next one is an attribute, in which case
 
795
                        // we need to handle it here.
 
796
                        if (isAttribute) {
 
797
                            // Now go find an attribute.
 
798
                            for (int i = 0, size = attrs.getLength();
 
799
                                 i < size; ++i) {
 
800
                                if (desiredElementName.equals(attrs.getLocalName(i)) ||
 
801
                                    desiredElementName.equals(attrs.getQName(i))) {
 
802
                                    // Found our attribute
 
803
                                    foundGotoNext();
 
804
                                    return;
 
805
                                }
 
806
                            }
 
807
                        }
 
808
                    }
 
809
                } else {
 
810
                    --desiredPosition;
 
811
                }
 
812
            }
 
813
        }
 
814
    }
 
815
}