package org.tigris.juxy.util; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.XMLFilterImpl; import org.tigris.juxy.XMLConstants; import java.io.OutputStream; import java.io.PrintWriter; import java.util.*; /** * Serializes incoming SAX events into the specified output stream. * <p/> * Serializer does not support internal DTD subset, doctypes and notations. * * @author Pavel Sher */ public class SAXSerializer extends XMLFilterImpl { private PrintWriter writer; private Map namespaces = new HashMap(); private Stack elemContents = new Stack(); private Stack entities = new Stack(); private boolean withinCDATA = false; private List currentNamespaces = new ArrayList(); public void setOutputStream(OutputStream os) { writer = new PrintWriter(os); } public void startDocument() throws SAXException { super.startDocument(); } public void endDocument() throws SAXException { super.endDocument(); getWriter().flush(); getWriter().close(); } private PrintWriter getWriter() { if (writer == null) throw new IllegalArgumentException("OutputStream is not set"); return writer; } public void startPrefixMapping(String prefix, String uri) throws SAXException { // super.startPrefixMapping(prefix, uri); namespaces.put(uri, prefix); currentNamespaces.add(uri); } public void endPrefixMapping(String prefix) throws SAXException { // super.endPrefixMapping(prefix); } public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { super.startElement(uri, localName, qName, atts); if (!withinEntity()) { completeStartingTagAndUpdateCounter(); String name = getElementName(localName, qName); getWriter().print("<" + name); if (atts.getLength() > 0) getWriter().print(" "); for (int i = 0; i < atts.getLength(); i++) { // skip all xmlns attributes if (XMLConstants.XMLNS_PREFIX_URI.equals(atts.getURI(i))) continue; String aname = atts.getQName(i); String avalue = encode(atts.getValue(i), true); getWriter().print(aname + "=\"" + avalue + "\""); if (i + 1 < atts.getLength()) getWriter().print(" "); } printXMLNSAttributes(atts); elemContents.push(new Integer(0)); getWriter().flush(); } } private void printXMLNSAttributes(Attributes atts) { Map ns = new HashMap(); for (int i = 0; i < atts.getLength(); i++) { if (!XMLConstants.XMLNS_PREFIX_URI.equals(atts.getURI(i))) continue; ns.put(atts.getQName(i), atts.getValue(i)); } Iterator nsIt = currentNamespaces.iterator(); while (nsIt.hasNext()) { String uri = (String) nsIt.next(); if (ns.containsValue(uri)) continue; String prefix = (String) namespaces.get(uri); String attrName = "xmlns"; if (prefix != null && prefix.length() > 0) attrName += ":" + prefix; if (ns.containsKey(attrName)) continue; ns.put(attrName, uri); } Iterator it = ns.entrySet().iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String attrName = (String) entry.getKey(); String attrValue = (String) entry.getValue(); getWriter().write(" " + attrName + "=\"" + attrValue + "\""); } currentNamespaces.clear(); } private String encode(String str, boolean encodeQuote) { String result = StringUtil.escapeXMLText(str); if (encodeQuote) result = StringUtil.escapeQuoteCharacter(result); return result; } private String getElementName(String localName, String qName) { return qName.length() > 0 ? qName : localName; } public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if (!withinEntity()) { Integer counter = (Integer) elemContents.pop(); if (counter.intValue() == 0) getWriter().print("/>"); else getWriter().print("</" + getElementName(localName, qName) + ">"); getWriter().flush(); } } public void characters(char ch[], int start, int length) throws SAXException { super.characters(ch, start, length); if (!withinEntity()) { completeStartingTagAndUpdateCounter(); printCharacters(ch, start, length, !withinCDATA); getWriter().flush(); } } private void completeStartingTagAndUpdateCounter() { if (elemContents.size() > 0) { Integer counter = (Integer) elemContents.pop(); if (counter.intValue() == 0) getWriter().print(">"); elemContents.push(new Integer(counter.intValue() + 1)); } } private void printCharacters(char[] ch, int start, int length, boolean withEncode) { if (withEncode) getWriter().print(encodeCharacters(ch, start, length)); else for (int i = 0; i < length; i++) getWriter().print(ch[start + i]); } private String encodeCharacters(char[] ch, int start, int length) { StringBuffer result = new StringBuffer(length); for (int i = 0; i < length; i++) { int pos = start + i; if (ch[pos] == '<') result.append("<"); else if (ch[pos] == '&') result.append("&"); else result.append(ch[pos]); } return result.toString(); } public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { super.ignorableWhitespace(ch, start, length); if (!withinEntity()) { completeStartingTagAndUpdateCounter(); printCharacters(ch, start, length, false); getWriter().flush(); } } public void processingInstruction(String target, String data) throws SAXException { super.processingInstruction(target, data); if (!withinEntity()) { completeStartingTagAndUpdateCounter(); getWriter().print("<?" + target + " " + encode(data, false) + "?>"); getWriter().flush(); } } /* public void startDTD(String name, String publicId, String systemId) throws SAXException { if (!withinEntity()) { getWriter().print("<!DOCTYPE " + name + " "); if (publicId != null) getWriter().print("PUBLIC \"" + publicId + "\" \"" + systemId + "\""); else if (systemId != null) getWriter().print("SYSTEM \"" + systemId + "\""); getWriter().flush(); } } */ /* public void endDTD() throws SAXException { if (!withinEntity()) { getWriter().print(">"); getWriter().flush(); } } */ /* public void startEntity(String name) throws SAXException { // skip system identifiers if (!name.startsWith("[")) { if (!withinEntity()) { completeStartingTagAndUpdateCounter(); getWriter().print("&" + name); getWriter().flush(); } entities.push(name); } } */ /* public void endEntity(String name) throws SAXException { if (!name.startsWith("[")) { entities.pop(); if (!withinEntity()) { getWriter().print(";"); getWriter().flush(); } } } */ /* public void startCDATA() throws SAXException { completeStartingTagAndUpdateCounter(); withinCDATA = true; getWriter().print("<![CDATA["); getWriter().flush(); } public void endCDATA() throws SAXException { getWriter().print("]]>"); getWriter().flush(); withinCDATA = false; } */ /* public void comment(char ch[], int start, int length) throws SAXException { completeStartingTagAndUpdateCounter(); getWriter().print("<!--"); printCharacters(ch, start, length, false); getWriter().print("-->"); getWriter().flush(); } */ private boolean withinEntity() { return !entities.isEmpty(); } }