package dk.brics.xact.operations;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import javax.xml.transform.sax.SAXSource;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLFilter;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import dk.brics.xact.Attribute;
import dk.brics.xact.AttributeGap;
import dk.brics.xact.Comment;
import dk.brics.xact.Element;
import dk.brics.xact.Node;
import dk.brics.xact.NodeVisitor;
import dk.brics.xact.ProcessingInstruction;
import dk.brics.xact.TemplateGap;
import dk.brics.xact.Text;
import dk.brics.xact.XML;
/**
* JAXP {@link SAXSource} for XML templates.
* Gaps in the XML templates are ignored.
*/ // TODO: optionally output gaps in desugared syntax
public class XMLSource extends SAXSource {
private static final String LEXICAL_HANDLER_NAME = "http://xml.org/sax/properties/lexical-handler";
private static final String FEATURE_NAMESPACES = "http://xml.org/sax/features/namespaces";
private XMLReader xmlreader = new SAXWriter();
/**
* Creates a JAXP {@link SAXSource} for the given XML template.
*/
public XMLSource(XML x) {
setDocument(x);
}
/**
* Sets the XML template.
*/
public void setDocument(XML x) {
super.setInputSource(new XMLInputSource(x));
setXMLReader(new SAXWriter());
}
/**
* Returns the <code>XMLReader</code> to be used for the JAXP <code>SAXSource</code>.
*/
@Override
public XMLReader getXMLReader() {
return xmlreader;
}
/**
* Sets the XMLReader used for the JAXP {@link SAXSource}.
*/
@Override
public void setXMLReader(XMLReader reader)
throws UnsupportedOperationException {
if (reader instanceof SAXWriter) {
this.xmlreader = reader;
} else if (reader instanceof XMLFilter) {
XMLFilter filter = (XMLFilter) reader;
while (true) {
XMLReader parent = filter.getParent();
if (parent instanceof XMLFilter) {
filter = (XMLFilter) parent;
} else {
break;
}
}
filter.setParent(xmlreader);
xmlreader = filter;
} else {
throw new UnsupportedOperationException();
}
}
@Override
public void setInputSource(InputSource inputSource) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
private class SAXWriter implements XMLReader {
private ContentHandler contentHandler;
private ErrorHandler errorHandler;
private DTDHandler dtdHandler;
private EntityResolver entityResolver;
private LexicalHandler lexicalHandler;
private AttributesImpl attributes = new AttributesImpl();
private Map<String, Boolean> features = new HashMap<String, Boolean>();
private Map<String, Object> properties = new HashMap<String, Object>();
public void setContentHandler(ContentHandler c) {
this.contentHandler = c;
}
public ContentHandler getContentHandler() {
return contentHandler;
}
public void setErrorHandler(ErrorHandler e) {
this.errorHandler = e;
}
public ErrorHandler getErrorHandler() {
return errorHandler;
}
public DTDHandler getDTDHandler() {
return dtdHandler;
}
public void setDTDHandler(DTDHandler d) {
dtdHandler = d;
}
public EntityResolver getEntityResolver() {
return entityResolver;
}
public void setEntityResolver(EntityResolver r) {
entityResolver = r;
}
public LexicalHandler getLexicalHandler() {
return lexicalHandler;
}
public void setLexicalHandler(LexicalHandler l) {
lexicalHandler = l;
}
private void parse(XML x) throws SAXException {
contentHandler.startDocument();
final Stack<Entry> stack = new Stack<Entry>(); // using heap stack, avoids deep recursive calls
stack.push(new Entry(Entry.Kind.START_NODE, x,null));
final NamespacePrefixTracker ns = new NamespacePrefixTracker();
while (!stack.isEmpty()) {
final Entry en = stack.pop();
switch (en.kind) {
case START_NODE:
try {
en.node.visitBy(new NodeVisitor() {
@Override
public void visit(Text n) {
String s = n.getString();
try {
contentHandler.characters(s.toCharArray(), 0, s.length());
} catch (SAXException e) {
throw new WrappedSAXException(e);
}
if (n.getNextSibling() != null)
stack.push(new Entry(Entry.Kind.START_NODE, n.getNextSibling(),null));
}
@Override
public void visit(Comment n) {
try {
lexicalHandler.comment(n.getValue().toCharArray(), 0, n.getValue().length());
} catch (SAXException e) {
throw new WrappedSAXException(e);
}
if (n.getNextSibling() != null)
stack.push(new Entry(Entry.Kind.START_NODE, n.getNextSibling(),null));
}
@Override
public void visit(ProcessingInstruction n) {
try {
contentHandler.processingInstruction(n.getTarget(), n.getData());
} catch (SAXException e) {
throw new WrappedSAXException(e);
}
if (n.getNextSibling() != null)
stack.push(new Entry(Entry.Kind.START_NODE, n.getNextSibling(),null));
}
@Override
public void visit(Element n) {
Map<String, String> nsdecls = ns.pushNamespaceDeclarations(n);
for (Map.Entry<String, String> e : nsdecls.entrySet()) {
try {
contentHandler.startPrefixMapping(e.getKey(), e.getValue());
} catch (SAXException e1) {
throw new WrappedSAXException(e1);
}
}
attributes.clear();
if (n.getFirstAttr() != null)
n.getFirstAttr().visitBy(this);
try {
String qName = getQName(ns, n.getNamespace(),n.getLocalName());
contentHandler.startElement(getStringValue(n.getNamespace()), getStringValue(n.getLocalName()), getStringValue(qName), attributes);
} catch (SAXException e) {
throw new WrappedSAXException(e);
}
stack.push(new Entry(Entry.Kind.END_ELEMENT, n,nsdecls));
if (n.getFirstChild() != null)
stack.push(new Entry(Entry.Kind.START_NODE, n.getFirstChild(),null));
}
@Override
public void visit(TemplateGap n) {
// ignore
if (n.getNextSibling() != null)
stack.push(new Entry(Entry.Kind.START_NODE, n.getNextSibling(),null));
}
@Override
public void visit(Attribute n) {
String qName = getQName(ns, n.getNamespace(),n.getLocalName());
attributes.addAttribute(getStringValue(n.getNamespace()), getStringValue(n.getLocalName()), getStringValue(qName), "CDATA", getStringValue(n.getValue()));
if (n.getNextAttr() != null)
n.getNextAttr().visitBy(this);
}
@Override
public void visit(AttributeGap n) {
// ignore
if (n.getNextAttr() != null)
n.getNextAttr().visitBy(this);
}
});
} catch (WrappedSAXException e) {
throw e.e;
}
break;
case END_ELEMENT:
Element n = (Element) (en.node);
String qName = getQName(ns, n.getNamespace(), n.getLocalName());
contentHandler.endElement(n.getNamespace(), n.getLocalName(), qName);
for (String p : en.nsdecls.keySet())
contentHandler.endPrefixMapping(p);
ns.popNamespaceDeclarations(en.nsdecls);
if (n.getNextSibling() != null)
stack.push(new Entry(Entry.Kind.START_NODE, n.getNextSibling(),null));
break;
}
}
contentHandler.endDocument();
}
/**
* Returns s if s is not null and "" otherwise
* @param s the string that may be null
* @return a string that is not null
*/
private String getStringValue(String s) {
if (s == null)
return "";
return s;
}
private String getQName(NamespacePrefixTracker ns, String namespace, String localName) {
if (namespace == null)
namespace = "";
String prefix = ns.getPrefix(namespace).peek();
String qName;
if (!prefix.equals("")) {
qName = prefix + ":" + localName;
} else {
qName = localName;
}
return qName;
}
public void parse(InputSource s) throws IOException, SAXException {
if (s instanceof XMLInputSource)
parse(((XMLInputSource) s).getSource());
else throw new IOException("Only XACT documents are supported");
}
public void parse(String s) throws IOException, SAXException {
throw new IOException("Only XACT documents are supported");
}
public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
Boolean answer = features.get(name);
return (answer != null) && answer.booleanValue();
}
public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
if (name.equals(FEATURE_NAMESPACES))
if (!value)
throw new SAXNotSupportedException("Namespace feature is always supported in XACT");
features.put(name, value);
}
public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
if (name.equals(LEXICAL_HANDLER_NAME))
return getLexicalHandler();
return properties.get(name);
}
public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
if (name.equals(LEXICAL_HANDLER_NAME))
setLexicalHandler((LexicalHandler) value);
else
properties.put(name, value);
}
}
private static class XMLInputSource extends InputSource {
private XML source;
private XMLInputSource(XML source) {
this.source = source;
}
public XML getSource() {
return source;
}
}
private static class Entry {
enum Kind {
START_NODE,
END_ELEMENT
}
final Kind kind;
final Node node;
final Map<String, String> nsdecls;
private Entry(Kind kind, Node node, Map<String, String> nsdecls) {
this.kind = kind;
this.node = node;
this.nsdecls = nsdecls;
}
}
private static class WrappedSAXException extends RuntimeException {
private static final long serialVersionUID = 1L;
private SAXException e;
WrappedSAXException(SAXException e) {
this.e = e;
}
}
}