/* * The JabaJaba class library * Copyright (C) 1997-2006 ASAMI, Tomoharu (asami@AsamiOffice.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package com.AsamiOffice.xml; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.Entity; import org.w3c.dom.EntityReference; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Notation; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import com.AsamiOffice.text.UString; import com.AsamiOffice.xml.visitor.DOMVisitorBase; import com.AsamiOffice.xml.visitor.DOMVisitorException; import com.AsamiOffice.xml.visitor.UDOMVisitor; /** * XMLMaker * * @since Oct. 27, 2000 * @version Sep. 20, 2005 * @author ASAMI, Tomoharu (asami@AsamiOffice.com) */ public class XMLMaker extends DOMVisitorBase { private Writer buffer_; private StringWriter stringWriter_; private String encoding_ = "UTF-8"; private boolean simpleXmlDeclaration_ = true; private String headComment_ = null; private String publicId_ = null; private String systemId_ = null; private boolean dom2_ = false; private boolean expandEntityReference_ = false; private boolean emptyElementTag_ = true; private boolean visual_ = false; private boolean autoMixed_ = true; private boolean isEscapeXmlChars_ = true; private int indent_ = 2; private int indentCount_ = 0; private int indentMixedMode_ = 0; private String lineSeparator_ = "\n"; // private String lineSeparator_ = System.getProperty("line.separator"); private boolean escapeCr_ = true; private boolean javaString_ = false; private Map attributeOrder_ = new HashMap(); private Set mixedElements_ = new HashSet(); public static void traverse(Node node, XMLMaker maker) { UDOMVisitor.traverse(node, maker); } public XMLMaker() { buffer_ = stringWriter_ = new StringWriter(); } public XMLMaker(Writer writer) { buffer_ = writer; stringWriter_ = null; } public void setEncoding(String encoding) { encoding_ = encoding; } public void setSimpleXmlDeclaration(boolean value) { simpleXmlDeclaration_ = value; } public void setPublicId(String id) { publicId_ = id; } public void setSystemId(String id) { systemId_ = id; } public void setDOM2(boolean dom2) { dom2_ = dom2; } public void setExpandEntityReference(boolean expand) { expandEntityReference_ = expand; } public void setEmptyElementTag(boolean empty) { emptyElementTag_ = empty; } public void setVisual(boolean visual) { visual_ = visual; } public void setAutoMixed(boolean autoMixed) { autoMixed_ = autoMixed; } public void setLineSeparator(String separator) { lineSeparator_ = separator; } public void setIndent(int indent) { indent_ = indent; } public void setJavaString(boolean javaString) { javaString_ = javaString; if (javaString) { lineSeparator_ = "\\n"; } } public String getText() { return (stringWriter_.toString()); } public boolean enter(Element element) { try { if (visual_ && indentMixedMode_ == 0) { _writeIndent(); } String tag = element.getTagName(); buffer_.write("<"); buffer_.write(tag); int attrIndent = indentCount_ + 1 + tag.length() + 1; NamedNodeMap attrs = element.getAttributes(); _makeAttributes(attrs, attrIndent, tag); if (element.getFirstChild() == null && emptyElementTag_) { buffer_.write("/>"); if (visual_) { buffer_.write(lineSeparator_); } return (false); } else { buffer_.write(">"); if (isMixedElement_(element)) { indentMixedMode_++; } if (_isIndent(element)) { buffer_.write(lineSeparator_); _indentUp(); } return (true); } } catch (IOException e) { throw (new DOMVisitorException(e)); } } private void _makeAttributes( NamedNodeMap attrMap, int attrIndent, String elementName ) throws IOException { Attr[] attrs = sortAttrs_(elementName, attrMap); if (visual_) { _makeAttributesVisual(attrs, attrIndent); } else { _makeAttributesPlain(attrs); } } private Attr[] sortAttrs_(String elementName, NamedNodeMap attrMap) { if (visual_) { return (sortAttrsVisual_(elementName, attrMap)); } else { return (sortAttrsAlphabet_(attrMap)); } } private Attr[] sortAttrsAlphabet_(NamedNodeMap attrMap) { int nAttrs = attrMap.getLength(); Attr[] attrs = new Attr[nAttrs]; for (int i = 0;i < nAttrs;i++) { Attr attr = (Attr)attrMap.item(i); attrs[i] = attr; } return (attrs); } private Attr[] sortAttrsVisual_(String elementName, NamedNodeMap attrMap) { List order = (List)attributeOrder_.get(elementName); //System.out.println("sortAttrs_" + elementName + "/" + order); if (order == null) { return (sortAttrsAlphabet_(attrMap)); } else { List list = new ArrayList(); Object[] names = order.toArray(); int nAttrs = attrMap.getLength(); for (int i = 0; i < names.length; i++) { String name = (String)names[i]; for (int j = 0;j < nAttrs;j++) { Attr attr = (Attr)attrMap.item(j); if (name.equals(attr.getName())) { list.add(attr); break; } } } for (int i = 0;i < nAttrs;i++) { Attr attr = (Attr)attrMap.item(i); if (!order.contains(attr.getName())) { list.add(attr); } } Attr[] result = new Attr[list.size()]; return ((Attr[])list.toArray(result)); } } private void _makeAttributesVisual( Attr[] attrs, int attrIndent ) throws IOException { Attr nsDef = null; List nsDefs = new ArrayList(); List attrDefs = new ArrayList(); for (int i = 0;i < attrs.length;i++) { Attr attr = attrs[i]; if (attr.getSpecified()) { if ("xmlns".equals(attr.getName())) { nsDef = attr; } else if (attr.getName().startsWith("xmlns:")) { nsDefs.add(attr); } else { attrDefs.add(attr); } } } if (nsDef != null) { nsDefs.add(0, nsDef); } int attrDefsSize = attrDefs.size(); int nsDefsSize = nsDefs.size(); if (nsDefsSize > 0) { // Collections.sort(nsDefs); Attr attr = (Attr)nsDefs.get(0); buffer_.write(' '); enter(attr); leave(attr); for (int i = 1;i < nsDefsSize;i++) { buffer_.write(lineSeparator_); _writeIndent(attrIndent); attr = (Attr)nsDefs.get(i); enter(attr); leave(attr); } if (attrDefsSize > 0) { buffer_.write(lineSeparator_); _writeIndent(attrIndent); } } else { if (attrDefsSize > 0) { buffer_.write(' '); } } { // TODO String status = "init"; for (int i = 0;i < attrDefsSize;i++) { Attr attr = (Attr)attrDefs.get(i); if ("init".equals(status)) { enter(attr); leave(attr); if (_isLongString(attr)) { // buffer_.write(lineSeparator_); // _writeIndent(attrIndent); status = "cont-long"; } else { status = "cont"; } } else if ("cont".equals(status)) { if (_isLongString(attr)) { buffer_.write(lineSeparator_); _writeIndent(attrIndent); enter(attr); leave(attr); status = "cont-long"; } else { buffer_.write(' '); enter(attr); leave(attr); status = "cont"; } } else if ("cont-long".equals(status)) { buffer_.write(lineSeparator_); _writeIndent(attrIndent); if (_isLongString(attr)) { enter(attr); leave(attr); status = "cont-long"; } else { enter(attr); leave(attr); status = "cont"; } } else { throw (new InternalError()); } } } /* } else { for (int i = 0;i < attrs.length;i++) { Attr attr = attrs[i]; if (attr.getSpecified()) { buffer_.write(' '); enter(attr); leave(attr); } } } */ } private boolean _isLongString(Attr attr) { String name = attr.getName(); String value = attr.getValue(); if (value != null) { return (name.length() + value.length() > 20); } else { return (name.length() > 20); } } private void _makeAttributesPlain(Attr[] attrs) throws IOException { for (int i = 0;i < attrs.length;i++) { Attr attr = attrs[i]; if (attr.getSpecified()) { buffer_.write(' '); enter(attr); leave(attr); } } } private boolean _isIndent(Element element) { if (!visual_) { return (false); } if (indentMixedMode_ > 0) { return (false); } if (hasElement_(element)) { return (true); } else { return (false); } } private boolean hasElement_(Element element) { NodeList children = element.getChildNodes(); int size = children.getLength(); for (int i = 0; i < size; i++) { if (children.item(i) instanceof Element) { return (true); } } return (false); } private boolean isMixedElement_(Element element) { if (wouldBeMixedElement_(element)) { return (true); } String localName = element.getLocalName(); if (localName == null) { localName = element.getTagName(); } return (mixedElements_.contains(localName)); } private boolean wouldBeMixedElement_(Element element) { if (!autoMixed_) { return (false); } NodeList children = element.getChildNodes(); int size = children.getLength(); for (int i = 0;i < size;i++) { Node child = children.item(i); if (children instanceof Text) { return (true); } } return (false); } public void leave(Element element) { String tag = element.getTagName(); try { if (isMixedElement_(element)) { indentMixedMode_--; if (indentMixedMode_ > 0) { buffer_.write("</" + tag + ">"); } else { buffer_.write("</" + tag + ">"); buffer_.write(lineSeparator_); } } else if (indentMixedMode_ > 0) { buffer_.write("</" + tag + ">"); } else if (_isIndent(element)) { _indentDown(); _writeIndent(); buffer_.write("</" + tag + ">"); buffer_.write(lineSeparator_); } else if (visual_) { buffer_.write("</" + tag + ">"); buffer_.write(lineSeparator_); } else { buffer_.write("</" + tag + ">"); } } catch (IOException e) { throw (new DOMVisitorException(e)); } } public boolean enter(Attr attr) { try { buffer_.write(attr.getName()); if (javaString_) { buffer_.write("='"); buffer_.write(UDOM.escapeAttrQuot(attr.getValue())); buffer_.write('\''); } else { buffer_.write("=\""); buffer_.write(UDOM.escapeAttrQuot(attr.getValue())); buffer_.write('\"'); } } catch (IOException e) { throw (new DOMVisitorException(e)); } return (true); } public void leave(Attr attr) { } public boolean enter(Text text) { try { String string; if (!isEscapeXmlChars_) { string = text.getData(); } else if (escapeCr_) { string = UDOM.escapeCharDataCr(text.getData()); } else { string = UDOM.escapeCharData(text.getData()); } if (javaString_) { appendJavaString_(string); } else { buffer_.write(string); } } catch (IOException e) { throw (new DOMVisitorException(e)); } return (true); } private void appendJavaString_(String string) throws IOException { char[] chars = string.toCharArray(); for (int i = 0;i < chars.length;i++) { char c = chars[i]; if (c == '\n') { buffer_.write("\\n"); } else if (c == '"') { buffer_.write("\\\""); } else { buffer_.write(c); } } } public void leave(Text text) { } public boolean enter(CDATASection cdata) { try { buffer_.write("<![CDATA["); buffer_.write(cdata.getData()); buffer_.write("]]>"); } catch (IOException e) { throw (new DOMVisitorException(e)); } return (true); } public void leave(CDATASection cdata) { } public boolean enter(EntityReference entityRef) { try { if (expandEntityReference_ && UDOM.isParsedEntity(entityRef)) { return (true); } else { buffer_.write("&"); buffer_.write(entityRef.getNodeName()); buffer_.write(";"); return (false); } } catch (IOException e) { throw (new DOMVisitorException(e)); } } public void leave(EntityReference entityRef) { } public boolean enter(Entity entity) { try { String name = entity.getNodeName(); String pid = entity.getPublicId(); String sid = entity.getSystemId(); String notation = entity.getNotationName(); if (sid != null) { UXMLMaker.makeUnparsedEntity(name, pid, sid, notation, buffer_); } else { buffer_.write("<!ENTITY "); buffer_.write(name); buffer_.write(" \""); XMLMaker entityMaker = new XMLMaker(); UDOMVisitor.traverseChildren(entity, entityMaker); buffer_.write(UDOM.escapeEntityQuot(entityMaker.getText())); buffer_.write("\""); buffer_.write(">"); } return (false); } catch (IOException e) { throw (new DOMVisitorException(e)); } } public void leave(Entity entity) { } public boolean enter(ProcessingInstruction pi) { try { UXMLMaker.makeProcessingInstruction( pi.getTarget(), pi.getData(), buffer_); return (true); } catch (IOException e) { throw (new DOMVisitorException(e)); } } public void leave(ProcessingInstruction pi) { } public boolean enter(Comment comment) { try { buffer_.write("<!--"); buffer_.write(comment.getData()); buffer_.write("-->"); return (true); } catch (IOException e) { throw (new DOMVisitorException(e)); } } public void leave(Comment comment) { } public boolean enter(Document doc) { if (javaString_) { return (true); } String name = doc.getDocumentElement().getTagName(); try { if (!(simpleXmlDeclaration_ && encoding_.toUpperCase().equals("UTF-8"))) { buffer_.write("<?xml version=\"1.0\" encoding=\""); buffer_.write(encoding_); buffer_.write("\" ?>"); buffer_.write(lineSeparator_); } makeHeadComment_(); if (systemId_ != null) { buffer_.write("<!DOCTYPE "); buffer_.write(name); if (publicId_ != null) { buffer_.write(" PUBLIC \""); buffer_.write(publicId_); buffer_.write("\""); buffer_.write(" \""); buffer_.write(systemId_); buffer_.write("\""); } else { buffer_.write(" SYSTEM \""); buffer_.write(systemId_); buffer_.write("\""); } buffer_.write(">"); buffer_.write(lineSeparator_); } return (true); } catch (IOException e) { throw (new DOMVisitorException(e)); } } private void makeHeadComment_() throws IOException { if (headComment_ == null) { return; } buffer_.write("<!--"); buffer_.write(lineSeparator_); int size = headComment_.length(); for (int i = 0;i < size;i++) { int c = headComment_.charAt(i); if (c == '\n') { buffer_.write(lineSeparator_); } else { buffer_.write((char)c); } } buffer_.write("-->"); buffer_.write(lineSeparator_); } public void leave(Document doc) { } public boolean enter(DocumentType doctype) { if (systemId_ != null) { return (true); } try { if (dom2_) { String name = doctype.getName(); String publicId = doctype.getPublicId(); String systemId = doctype.getSystemId(); String internalSubset = doctype.getInternalSubset(); buffer_.write("<!DOCTYPE "); buffer_.write(name); if (publicId != null) { buffer_.write(" PUBLIC \""); buffer_.write(publicId); buffer_.write("\""); } if (systemId != null) { buffer_.write(" SYSTEM \""); buffer_.write(systemId); buffer_.write("\""); } if (internalSubset != null) { buffer_.write(" ["); buffer_.write(internalSubset); buffer_.write("]"); } buffer_.write(">"); buffer_.write(lineSeparator_); return (true); } else { String name = doctype.getName(); NamedNodeMap entities = doctype.getEntities(); NamedNodeMap notations = doctype.getNotations(); buffer_.write("<!DOCTYPE "); buffer_.write(name); if (entities != null && entities.getLength() > 0 || notations != null && notations.getLength() > 0) { buffer_.write(" ["); int nEntities = entities.getLength(); for (int i = 0; i < nEntities; i++) { XMLMaker entityMaker = new XMLMaker(); UDOMVisitor.traverse(entities.item(i), entityMaker); buffer_.write(entityMaker.getText()); } int nNotations = notations.getLength(); for (int i = 0; i < nNotations; i++) { enter((Notation)notations.item(i)); leave((Notation)notations.item(i)); } buffer_.write("]"); } buffer_.write(">"); buffer_.write(lineSeparator_); } return (true); } catch (IOException e) { throw (new DOMVisitorException(e)); } } public void leave(DocumentType doctype) { } public boolean enter(DocumentFragment docfrag) { return (true); } public void leave(DocumentFragment docfrag) { } public boolean enter(Notation notation) { try { String name = notation.getNodeName(); String pid = notation.getPublicId(); String sid = notation.getSystemId(); UXMLMaker.makeNotation(name, pid, sid, buffer_); return (true); } catch (IOException e) { throw (new DOMVisitorException(e)); } } public void leave(Notation notation) { } public boolean enter(Node node) { throw (new InternalError()); } public void leave(Node node) { throw (new InternalError()); } private void _indentUp() { indentCount_ += indent_; } private void _indentDown() { indentCount_ -= indent_; } private void _writeIndent() throws IOException { for (int i = 0; i < indentCount_; i++) { buffer_.write(" "); } } private void _writeIndent(int count) throws IOException { for (int i = 0;i < count;i++) { buffer_.write(" "); } } public void setHeadComment(String headComment) { headComment_ = headComment; } public void setEscapeXmlChars(boolean escape) { isEscapeXmlChars_ = escape; } public void addAttributeOrder(String localName, String values) { attributeOrder_.put(localName, Arrays.asList(UString.getTokens(values))); } public void addMixedElement(String localName) { mixedElements_.add(localName); } }