/**
* Copyright 2006-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mybatis.generator.internal;
import static org.mybatis.generator.internal.util.messages.Messages.getString;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.mybatis.generator.exception.ShellException;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
/**
* This class is used to generate a String representation of an XML document. It
* is very much based on the class dom.Writer from the Apache Xerces examples,
* but I've simplified and updated it.
*
* @author Andy Clark, IBM (Original work)
* @author Jeff Butler (derivation)
*/
public class DomWriter {
/** The print writer. */
protected PrintWriter printWriter;
/** The is xm l11. */
protected boolean isXML11;
/**
* Instantiates a new dom writer.
*/
public DomWriter() {
super();
}
/**
* To string.
*
* @param document
* the document
* @return the string
* @throws ShellException
* the shell exception
*/
public synchronized String toString(Document document)
throws ShellException {
StringWriter sw = new StringWriter();
printWriter = new PrintWriter(sw);
write(document);
String s = sw.toString();
return s;
}
/**
* Returns a sorted list of attributes.
*
* @param attrs
* the attrs
* @return the attr[]
*/
protected Attr[] sortAttributes(NamedNodeMap attrs) {
int len = (attrs != null) ? attrs.getLength() : 0;
Attr array[] = new Attr[len];
for (int i = 0; i < len; i++) {
array[i] = (Attr) attrs.item(i);
}
for (int i = 0; i < len - 1; i++) {
String name = array[i].getNodeName();
int index = i;
for (int j = i + 1; j < len; j++) {
String curName = array[j].getNodeName();
if (curName.compareTo(name) < 0) {
name = curName;
index = j;
}
}
if (index != i) {
Attr temp = array[i];
array[i] = array[index];
array[index] = temp;
}
}
return array;
}
/**
* Normalizes and prints the given string.
*
* @param s
* the s
* @param isAttValue
* the is att value
*/
protected void normalizeAndPrint(String s, boolean isAttValue) {
int len = (s != null) ? s.length() : 0;
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
normalizeAndPrint(c, isAttValue);
}
}
/**
* Normalizes and print the given character.
*
* @param c
* the c
* @param isAttValue
* the is att value
*/
protected void normalizeAndPrint(char c, boolean isAttValue) {
switch (c) {
case '<': {
printWriter.print("<"); //$NON-NLS-1$
break;
}
case '>': {
printWriter.print(">"); //$NON-NLS-1$
break;
}
case '&': {
printWriter.print("&"); //$NON-NLS-1$
break;
}
case '"': {
// A '"' that appears in character data
// does not need to be escaped.
if (isAttValue) {
printWriter.print("""); //$NON-NLS-1$
} else {
printWriter.print('"');
}
break;
}
case '\r': {
// If CR is part of the document's content, it
// must be printed as a literal otherwise
// it would be normalized to LF when the document
// is reparsed.
printWriter.print("
"); //$NON-NLS-1$
break;
}
case '\n': {
// If LF is part of the document's content, it
// should be printed back out with the system default
// line separator. XML parsing forces \n only after a parse,
// but we should write it out as it was to avoid whitespace
// commits on some version control systems.
printWriter.print(System.getProperty("line.separator")); //$NON-NLS-1$
break;
}
default: {
// In XML 1.1, control chars in the ranges [#x1-#x1F, #x7F-#x9F]
// must be escaped.
//
// Escape space characters that would be normalized to #x20 in
// attribute values
// when the document is reparsed.
//
// Escape NEL (0x85) and LSEP (0x2028) that appear in content
// if the document is XML 1.1, since they would be normalized to LF
// when the document is reparsed.
if (isXML11
&& ((c >= 0x01 && c <= 0x1F && c != 0x09 && c != 0x0A)
|| (c >= 0x7F && c <= 0x9F) || c == 0x2028)
|| isAttValue && (c == 0x09 || c == 0x0A)) {
printWriter.print(""); //$NON-NLS-1$
printWriter.print(Integer.toHexString(c).toUpperCase());
printWriter.print(';');
} else {
printWriter.print(c);
}
}
}
}
/**
* Extracts the XML version from the Document.
*
* @param document
* the document
* @return the version
*/
protected String getVersion(Document document) {
if (document == null) {
return null;
}
return document.getXmlVersion();
}
/**
* Write any node.
*
* @param node
* the node
* @throws ShellException
* the shell exception
*/
protected void writeAnyNode(Node node) throws ShellException {
// is there anything to do?
if (node == null) {
return;
}
short type = node.getNodeType();
switch (type) {
case Node.DOCUMENT_NODE:
write((Document) node);
break;
case Node.DOCUMENT_TYPE_NODE:
write((DocumentType) node);
break;
case Node.ELEMENT_NODE:
write((Element) node);
break;
case Node.ENTITY_REFERENCE_NODE:
write((EntityReference) node);
break;
case Node.CDATA_SECTION_NODE:
write((CDATASection) node);
break;
case Node.TEXT_NODE:
write((Text) node);
break;
case Node.PROCESSING_INSTRUCTION_NODE:
write((ProcessingInstruction) node);
break;
case Node.COMMENT_NODE:
write((Comment) node);
break;
default:
throw new ShellException(getString(
"RuntimeError.18", Short.toString(type))); //$NON-NLS-1$
}
}
/**
* Write.
*
* @param node
* the node
* @throws ShellException
* the shell exception
*/
protected void write(Document node) throws ShellException {
isXML11 = "1.1".equals(getVersion(node)); //$NON-NLS-1$
if (isXML11) {
printWriter.println("<?xml version=\"1.1\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$
} else {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$
}
printWriter.flush();
write(node.getDoctype());
write(node.getDocumentElement());
}
/**
* Write.
*
* @param node
* the node
* @throws ShellException
* the shell exception
*/
protected void write(DocumentType node) throws ShellException {
printWriter.print("<!DOCTYPE "); //$NON-NLS-1$
printWriter.print(node.getName());
String publicId = node.getPublicId();
String systemId = node.getSystemId();
if (publicId != null) {
printWriter.print(" PUBLIC \""); //$NON-NLS-1$
printWriter.print(publicId);
printWriter.print("\" \""); //$NON-NLS-1$
printWriter.print(systemId);
printWriter.print('\"');
} else if (systemId != null) {
printWriter.print(" SYSTEM \""); //$NON-NLS-1$
printWriter.print(systemId);
printWriter.print('"');
}
String internalSubset = node.getInternalSubset();
if (internalSubset != null) {
printWriter.println(" ["); //$NON-NLS-1$
printWriter.print(internalSubset);
printWriter.print(']');
}
printWriter.println('>');
}
/**
* Write.
*
* @param node
* the node
* @throws ShellException
* the shell exception
*/
protected void write(Element node) throws ShellException {
printWriter.print('<');
printWriter.print(node.getNodeName());
Attr attrs[] = sortAttributes(node.getAttributes());
for (Attr attr : attrs) {
printWriter.print(' ');
printWriter.print(attr.getNodeName());
printWriter.print("=\""); //$NON-NLS-1$
normalizeAndPrint(attr.getNodeValue(), true);
printWriter.print('"');
}
if (node.getChildNodes().getLength() == 0) {
printWriter.print(" />"); //$NON-NLS-1$
printWriter.flush();
} else {
printWriter.print('>');
printWriter.flush();
Node child = node.getFirstChild();
while (child != null) {
writeAnyNode(child);
child = child.getNextSibling();
}
printWriter.print("</"); //$NON-NLS-1$
printWriter.print(node.getNodeName());
printWriter.print('>');
printWriter.flush();
}
}
/**
* Write.
*
* @param node
* the node
*/
protected void write(EntityReference node) {
printWriter.print('&');
printWriter.print(node.getNodeName());
printWriter.print(';');
printWriter.flush();
}
/**
* Write.
*
* @param node
* the node
*/
protected void write(CDATASection node) {
printWriter.print("<![CDATA["); //$NON-NLS-1$
String data = node.getNodeValue();
// XML parsers normalize line endings to '\n'. We should write
// it out as it was in the original to avoid whitespace commits
// on some version control systems
int len = (data != null) ? data.length() : 0;
for (int i = 0; i < len; i++) {
char c = data.charAt(i);
if (c == '\n') {
printWriter.print(System.getProperty("line.separator")); //$NON-NLS-1$
} else {
printWriter.print(c);
}
}
printWriter.print("]]>"); //$NON-NLS-1$
printWriter.flush();
}
/**
* Write.
*
* @param node
* the node
*/
protected void write(Text node) {
normalizeAndPrint(node.getNodeValue(), false);
printWriter.flush();
}
/**
* Write.
*
* @param node
* the node
*/
protected void write(ProcessingInstruction node) {
printWriter.print("<?"); //$NON-NLS-1$
printWriter.print(node.getNodeName());
String data = node.getNodeValue();
if (data != null && data.length() > 0) {
printWriter.print(' ');
printWriter.print(data);
}
printWriter.print("?>"); //$NON-NLS-1$
printWriter.flush();
}
/**
* Write.
*
* @param node
* the node
*/
protected void write(Comment node) {
printWriter.print("<!--"); //$NON-NLS-1$
String comment = node.getNodeValue();
if (comment != null && comment.length() > 0) {
normalizeAndPrint(comment, false);
}
printWriter.print("-->"); //$NON-NLS-1$
printWriter.flush();
}
}