2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
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]"
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.
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.
42
package org.netbeans.modules.schema2beans;
48
public class XMLUtil {
52
* Takes some text to be printed into an XML stream and escapes any
53
* characters that might make it invalid XML (like '<').
55
public static void printXML(StringBuffer out, String msg) {
56
printXML(out, msg, true);
59
public static void printXML(StringBuffer out, String msg, boolean attribute) {
60
if (msg == null) return;
61
appendLine(out, msg, attribute);
64
/** @deprecated this public method is not expected to be used by schema2beans client
66
public static void printXML(StringBuffer out, char msg, boolean attribute) {
89
* Takes some text to be printed into an XML stream and escapes any
90
* characters that might make it invalid XML (like '<').
92
public static void writeXML(java.io.Writer out, String msg) throws java.io.IOException {
93
writeXML(out, msg, true);
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);
101
/** @deprecated this public method is not expected to be used by schema2beans client
103
public static void writeXML(java.io.Writer out, char msg, boolean attribute) throws java.io.IOException {
110
else if (attribute) {
113
else if (msg == '\'')
115
else if (msg == '\n')
117
else if (msg == '\t')
125
public static boolean shouldEscape(char c) {
135
public static boolean shouldEscape(String s) {
138
int msgLength = s.length();
139
for (int i = 0; i < msgLength; ++i) {
140
char c = s.charAt(i);
148
* Takes some text to be printed into an XML stream and escapes any
149
* characters that might make it invalid XML (like '<').
151
public static void printXML(java.io.Writer out, String msg) throws java.io.IOException {
152
printXML(out, msg, true);
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);
160
private static void appendLine(StringBuffer out, String msg, boolean attribute) {
161
out.append(convertChars(msg,attribute));
164
private static void appendLine(java.io.Writer out, String msg, boolean attribute) throws java.io.IOException {
165
out.write(convertChars(msg,attribute));
168
private static String convertChars(String msg, boolean attribute) {
170
if (msg.indexOf("&")>=0) //NOI18N
171
result = result.replaceAll("&","&"); //NOI18N
172
if (msg.indexOf("<")>=0) //NOI18N
173
result = result.replaceAll("<","<"); //NOI18N
174
if (msg.indexOf(">")>=0) //NOI18N
175
result = result.replaceAll(">",">"); //NOI18N
176
if (attribute) { //NOI18N
177
if (msg.indexOf("\"")>=0) //NOI18N
178
result = result.replaceAll("\"","""); //NOI18N
179
if (msg.indexOf("'")>=0) //NOI18N
180
result = result.replaceAll("'","'"); //NOI18N
181
if (msg.indexOf("\n")>=0) //NOI18N
182
result = result.replaceAll("\n","
"); //NOI18N
183
if (msg.indexOf("\t")>=0) //NOI18N
184
result = result.replaceAll("\t","	"); //NOI18N
189
/** @deprecated this public method is not expected to be used by schema2beans client
191
public static void printXML(java.io.Writer out, char msg, boolean attribute) throws java.io.IOException {
198
else if (attribute) {
201
else if (msg == '\'')
203
else if (msg == '\n')
205
else if (msg == '\t')
214
public static class DOMWriter {
215
private java.io.Writer out;
216
private boolean writeCData = false;
217
private String docTypePublic;
218
private String docTypeSystem;
223
public void setWriter(java.io.Writer out) {
227
public void setWriteCData(boolean value) {
231
public void setDocTypePublic(String value) {
232
docTypePublic = value;
235
public void setDocTypeSystem(String value) {
236
docTypeSystem = value;
240
* Same as write(OutputStream os, Document document)
241
* where os = the file's OutputStream.
243
public void write(File f, Document document) throws java.io.IOException {
244
OutputStream fout = new FileOutputStream(f);
246
write(fout, document);
253
* Same as write(OutputStream os, String encoding, Document document)
254
* where encoding == null.
256
public void write(OutputStream os, Document document) throws java.io.IOException {
257
write(os, null, document);
261
* Create an output Writer based on the OutputStream using the
262
* encoding (use "UTF-8" if encoding == null), then write the DOM
265
public void write(OutputStream os, String encoding, Document document) throws java.io.IOException {
266
if (encoding == null)
268
out = new BufferedWriter(new OutputStreamWriter(os, encoding));
269
write(document, encoding);
273
* Assumes that the output Writer has already been set.
275
public void write(Document document) throws java.io.IOException {
276
write(document, null);
280
* Assumes that the output Writer has already been set.
281
* @param encoding goes into the XML header.
283
public void write(Document document, String encoding) throws java.io.IOException {
284
write(document, encoding, true);
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.
293
public void write(Document document, String encoding,
294
boolean writeHeader) throws java.io.IOException {
296
out.write("<?xml version=\"1.0\""); // NOI18N
297
if (encoding != null) {
298
out.write(" encoding=\""+encoding+"\"?>\n"); // NOI18N
300
out.write(" encoding=\"UTF-8\"?>\n"); // NOI18N
302
if (docTypePublic != null || docTypeSystem != null) {
303
String docName = getDocTypeName(document);
304
DocumentType docType = document.getDoctype();
305
NamedNodeMap entities = null;
307
entities = docType.getEntities();
308
write(docName, docTypePublic, docTypeSystem, entities);
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) {
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)) {
333
public void write(Node node) throws java.io.IOException {
334
boolean needsReturnBetweenChildren = false;
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()) )) {
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);
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());
367
int length = children.getLength();
368
for (int i = 0; i < length; ++i) {
369
write(children.item(i));
370
if (needsReturnBetweenChildren)
373
if (node instanceof Element) {
374
out.write("</"+node.getNodeName()+">");
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.
384
write(docType.getName(), docType.getPublicId(),
385
docType.getSystemId(), docType.getEntities());
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
398
if (systemId != null) {
399
out.write(" \""); // NOI18N
400
XMLUtil.printXML(out, systemId);
401
out.write("\""); // NOI18N
403
if (entities != null) {
404
int length = entities.getLength();
406
out.write(" ["); // NOI18N
407
for (int i = 0; i < length; ++i) {
408
Node node = entities.item(i);
411
out.write("]"); // NOI18N
414
out.write(">"); // NOI18N
417
protected void write(Comment comment) throws java.io.IOException {
418
// Does not need to have anything escaped (no printXML).
420
String text = comment.getNodeValue();
421
// A comment is not allow to have "--" inside of it.
422
int pos = text.indexOf("--");
424
out.write(text.substring(0, pos));
425
out.write("--");
426
text = text.substring(pos+2, text.length());
427
pos = text.indexOf("--");
433
protected void write(Entity entity) throws java.io.IOException {
434
out.write("<!ENTITY "+entity.getNodeName());
436
We don't seem to be able to get any useful info out of the
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());
449
out.write(" UNKNOWN>");
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.
458
out.write("<?"+pi.getTarget()+" "+pi.getData()+"?>");
462
* This is used to print attributes.
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);
469
out.write(node.getNodeName());
471
XMLUtil.printXML(out, node.getNodeValue());
476
protected void printXML(String msg, boolean attribute) throws java.io.IOException {
477
if (writeCData && msg.indexOf("]]>") < 0) {
478
boolean shouldEscape = XMLUtil.shouldEscape(msg);
480
out.write("<![CDATA[");
485
XMLUtil.printXML(out, msg, attribute);
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();
501
// Otherwise, check the first node of the actual document
502
Node rootNode = doc.getDocumentElement();
503
return rootNode.getNodeName();
507
* Reformat the DOM graph to make it look like pretty XML.
509
* @param doc The Document to create new TextNodes from.
510
* @param indent The String used to indent per level
512
public static void reindent(Document doc, String indent) {
513
reindent(doc, doc, -1, indent);
517
* Reformat the DOM graph to make it look like pretty XML.
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
525
public static boolean reindent(Document doc, Node node,
526
int level, String indent) {
527
String nodeValue = node.getNodeValue();
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;
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) {
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.
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+"'");
559
// Check to make sure the last child node is a text node.
560
// If not, insert the correct spacing at the end.
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));
567
//System.out.println("node.getNodeName="+node.getNodeName()+" children.length="+length);
569
boolean shouldBeTextNode = true; // This alternates
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) {
577
String childNodeValue = childNode.getNodeValue().intern();
579
// We have a single text child, don't mess with
584
textNode = (Text) childNode;
585
// Need to make sure it has the correct whitespace
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);
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);
601
shouldBeTextNode ^= true;
603
// Need to insert a whitespace node
604
//System.out.println("Need to insert a whitespace node before "+childNode.getNodeName()+": "+childNode.getNodeValue());
606
//System.out.println("It's a final one!");
607
node.insertBefore(doc.createTextNode(idealChildWhitespace), childNode);
608
node.appendChild(doc.createTextNode(idealFinalWhitespace));
611
//System.out.println("Not final.");
612
node.insertBefore(doc.createTextNode(idealChildWhitespace), childNode);
615
// We updated our list while going thru it at the same
616
// time, so update our indices to account for the
624
// The last whitespace node is correct, so this one
626
//System.out.println("Extra unneeded whitespace");
627
node.removeChild(childNode);
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);
644
// This is just right.
645
//System.out.println("This is just right.");
646
shouldBeTextNode ^= true;
652
// Let my caller know if I'm a Text node that has only whitespace
654
if (node instanceof Text) {
655
if (nodeValue == null)
657
return (nodeValue.trim().equals(""));
662
protected static void printLevel(StringBuffer out, int level, String indent) {
663
for (int i = 0; i < level; ++i) {
669
* Given an XPath expression, find it's location in a Document
670
* @return null if not found
672
public static Locator findLocationXPath(InputSource in, String xpathExpr) throws IOException, org.xml.sax.SAXException {
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);
682
XPathLocator locator = new XPathLocator(xpathExpr);
683
parser.setContentHandler(locator);
685
return locator.getDocumentLocator();
688
/** Test if character can be in attr value
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
700
private static class XPathLocator
701
extends org.xml.sax.helpers.DefaultHandler implements ContentHandler {
702
private String xpathExpr;
703
private String[] xpathParts;
705
private String desiredElementName;
706
private int desiredPosition;
707
private boolean isAttribute;
708
private Locator locator = null;
709
private Locator resultLocator = null;
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.
721
private void setElementName() {
722
desiredElementName = xpathParts[partNum].trim();
725
int startPos = desiredElementName.indexOf('[');
726
int endPos = desiredElementName.indexOf(']');
727
//System.out.println("desiredElementName="+desiredElementName);
730
throw new IllegalArgumentException("XPath subexpression ("+desiredElementName+") is missing an ending ']'.");
731
String subExpr = desiredElementName.substring(startPos+1,
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()));
738
boolean allDigits = subExpr.length() > 0;
739
for (int i = 0; i < subExpr.length(); ++i) {
740
if (!Character.isDigit(subExpr.charAt(i))) {
746
desiredPosition = Integer.parseInt(subExpr);
748
throw new UnsupportedOperationException("XPath ("+subExpr+" in "+xpathExpr+") not supported.");
751
} else if (desiredElementName.startsWith("@")) {
753
desiredElementName = desiredElementName.substring(1, desiredElementName.length());
755
//System.out.println("desiredElementName="+desiredElementName);
759
* @return true means done
761
private boolean foundGotoNext() {
763
if (partNum >= xpathParts.length) {
764
// Found the final one!
765
resultLocator = new org.xml.sax.helpers.LocatorImpl(locator);
768
// goto the next subexpression
774
public Locator getDocumentLocator() {
775
return resultLocator;
778
public void setDocumentLocator(Locator locator) {
779
this.locator = locator;
782
public void startElement(String namespaceURI, String localName,
783
String rawName, Attributes attrs) throws SAXException {
784
if (resultLocator != null) {
785
// It's already found.
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.
797
// Now go find an attribute.
798
for (int i = 0, size = attrs.getLength();
800
if (desiredElementName.equals(attrs.getLocalName(i)) ||
801
desiredElementName.equals(attrs.getQName(i))) {
802
// Found our attribute