package dk.brics.xact.operations;
import java.util.LinkedList;
import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.DefaultHandler2;
import dk.brics.misc.Origin;
import dk.brics.xact.AttrNode;
import dk.brics.xact.Attribute;
import dk.brics.xact.AttributeGap;
import dk.brics.xact.Comment;
import dk.brics.xact.Element;
import dk.brics.xact.NamespaceDecl;
import dk.brics.xact.Node;
import dk.brics.xact.ProcessingInstruction;
import dk.brics.xact.TempNode;
import dk.brics.xact.TemplateGap;
import dk.brics.xact.Text;
import dk.brics.xact.XML;
/**
* A SAX parser that given a stream SAX events generates an XACT document
*/
public class ParseHandler extends DefaultHandler2 {
private final Stack<Stack<Node>> siblings = new Stack<Stack<Node>>();
private XML root;
private Locator locator;
private final Origin base;
private LinkedList<NamespaceDecl> nsDecls = new LinkedList<NamespaceDecl>();
/**
* Constructs a new parse handler.
*/
public ParseHandler(Origin base) {
this.base = base;
}
/**
* Returns the resulting XML template.
*/
public XML getRoot() {
return root;
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
private Attribute makeAttributes(Attributes as, Origin origin) {
Attribute a = null;
for (int i = 0; i < as.getLength(); i++) {
String ns = as.getURI(i);
if (ns.length() == 0) {
ns = null;
}
if ((ns == null || ns.equals("http://www.w3.org/2000/xmlns/")) && as.getLocalName(i).equals("xmlns")) { // TODO: Why do we need to filter these in the results from XSL transformation? startPrefixMapping is called correctly
continue;
}
a = new Attribute(ns, as.getLocalName(i), as.getValue(i), a, origin);
}
return a;
}
private Origin getOrigin() { // TODO: improve origin tracking (messed up by wrapAndConvertGaps)
Origin origin = null;
if (base != null) {
int line = base.getLine();
int col = base.getColumn();
if (locator.getLineNumber() == 1) {
col += locator.getColumnNumber();
} else {
line += locator.getLineNumber();
col = locator.getColumnNumber();
}
origin = new Origin(base.getFile(), line, col);
}
return origin;
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
if (!uri.equals(XMLParser.XACT_NAMESPACE)) {
NamespaceDecl newNs = new NamespaceDecl(prefix, uri);
nsDecls.addFirst(newNs); // the list will now holds the declarations in reverse order
}
}
@Override
public void startDocument() {
siblings.push(new Stack<Node>());
}
@Override
public void endDocument() {
Stack<Node> stack = siblings.peek();
if (stack.peek() instanceof Element)
root = (Element) stack.peek(); // TODO: ignores PI/comments before/after root element!!!
else {
root = XML.concat(stack);
}
}
@Override
public void startElement(String uri, String localname, String name, Attributes attrs) {
Origin origin = getOrigin();
Node n;
if (uri.equals(XMLParser.XACT_NAMESPACE)) {
if (localname.equals("tgap")) {
n = new TemplateGap(attrs.getValue("", "gap"), attrs.getValue("", "type"), origin);
} else { // agap
String ns = null;
String aname = null;
String gap = null;
for (int i = 0; i < attrs.getLength(); i++) {
aname = attrs.getLocalName(i);
if (!attrs.getURI(i).equals(XMLParser.XACT_NAMESPACE)) {
ns = attrs.getURI(i);
if (ns.length() == 0) {
ns = null;
}
gap = attrs.getValue(i);
break;
}
}
String type = attrs.getValue(XMLParser.XACT_NAMESPACE, "type");
n = new AttributeGap(ns, aname, gap, type, origin);
}
} else {
NamespaceDecl lns = null;
for (NamespaceDecl ns : nsDecls) {
lns = new NamespaceDecl(ns.getPrefix(), ns.getNamespace(),lns, null);
}
n = new Element(uri, localname, makeAttributes(attrs, origin), null, lns, null, origin);
nsDecls = new LinkedList<NamespaceDecl>();
}
siblings.peek().push(n);
siblings.push(new Stack<Node>());
}
@Override
public void endElement(String uri, String localname, String name) {
Stack<Node> children = siblings.pop();
TempNode c = null;
Node n = siblings.peek().pop();
if (n instanceof Element) {
Element e = (Element) n;
AttrNode a = e.getFirstAttr();
while (!children.isEmpty()) {
Node m = children.pop();
if (m instanceof AttributeGap) { // element with agap child
a = ((AttributeGap) m).copy(a);
} else {
c = ((TempNode) m).copy(c);
}
}
n = e.copy(a, c, e.getNextSibling());
} else if (n instanceof AttributeGap) {
if (!siblings.peek().isEmpty()) {
Node m = siblings.peek().pop();
if (m instanceof Element) { // element with agap successor
Element f = (Element) m;
m = f.copy(((AttributeGap) n).copy(f.getFirstAttr()), f.getFirstChild(), f.getNextSibling());
n = null;
}
siblings.peek().push(m);
}
}
if (n != null) {
siblings.peek().push(n);
}
}
@Override
public void characters(char[] ch, int start, int length) {
siblings.peek().push(new Text(new String(ch, start, length), getOrigin()));
}
@Override
public void processingInstruction(String target, String data) {
siblings.peek().push(new ProcessingInstruction(target, data, getOrigin()));
}
@Override
public void comment(char[] ch, int start, int length) {
siblings.peek().push(new Comment(new String(ch, start, length), getOrigin()));
}
}