package dk.brics.xact.operations;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import dk.brics.automaton.Automaton;
import dk.brics.misc.Origin;
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.NodeVisitor;
import dk.brics.xact.ProcessingInstruction;
import dk.brics.xact.TemplateGap;
import dk.brics.xact.Text;
import dk.brics.xact.XML;
import dk.brics.xact.XMLException;
import dk.brics.xmlgraph.AttributeNode;
import dk.brics.xmlgraph.ChoiceNode;
import dk.brics.xmlgraph.ElementNode;
import dk.brics.xmlgraph.SequenceNode;
import dk.brics.xmlgraph.TextNode;
import dk.brics.xmlgraph.XMLGraph;
import dk.brics.xmlgraph.XMLGraphFragment;
/**
* Converter from XML templates to XML graphs.
*/
public class XMLGraphConverter {
private XMLGraphConverter() {}
/**
* Gap conversion flags.
*/
public enum GapConversion {
/**
* Ignore gap nodes.
*/
IGNORE,
/**
* Leave gaps open.
*/
OPEN,
/**
* Close gaps.
*/
CLOSED,
}
/**
* Converts the given XML template into a new XML graph.
* @param gaps if set, gaps are included, otherwise they are implicitly closed
* @param wrapchoices if set, extra choice nodes are added around attribute nodes and
* element nodes and above text nodes
*/
public static XMLGraph convert(XML x, GapConversion gaps, boolean wrapchoices) {
XMLGraph xg = new XMLGraph();
xg.useFragment(extend(xg, x, gaps, wrapchoices));
return xg;
}
private static final class PendingNode {
public Node node;
public SequenceNode parent;
public PendingNode(Node node, SequenceNode parent) {
this.node = node;
this.parent = parent;
}
}
/**
* Converts the given XML template by extending an existing XML graph.
* @param gaps if set, gaps are included, otherwise they are implicitly closed
*/
public static XMLGraphFragment extend(
final XMLGraph xg,
XML x,
final GapConversion gaps,
final boolean wrapchoices) {
final Collection<String> tgaps = new ArrayList<String>();
final Collection<String> agaps = new ArrayList<String>();
final Map<String,String> gap_types = new HashMap<String,String>();
final Map<Element,List<Integer>> contents = new HashMap<Element,List<Integer>>();
final Stack<PendingNode> pending = new Stack<PendingNode>();
List<Integer> top_contents = new ArrayList<Integer>();
SequenceNode top = new SequenceNode(top_contents, x.getOrigin());
xg.addNode(top);
pending.add(new PendingNode(x, top));
while (!pending.isEmpty()) {
final PendingNode m = pending.pop();
m.node.visitBy(new NodeVisitor() {
private void push(Node n, SequenceNode parent) {
if (n != null)
pending.push(new PendingNode(n, parent));
}
private dk.brics.xmlgraph.Node wrapChoice(dk.brics.xmlgraph.Node n) {
if (!(n instanceof ChoiceNode)) {
Collection<Integer> cs = new ArrayList<Integer>();
cs.add(n.getIndex());
n = new ChoiceNode(cs, n.getOrigin());
xg.addNode(n);
}
return n;
}
@Override
public void visit(Text n) {
dk.brics.xmlgraph.Node tn = new TextNode(Automaton.makeString(n.getString()), n.getOrigin());
xg.addNode(tn);
if (wrapchoices)
tn = wrapChoice(tn);
m.parent.addContent(tn.getIndex());
push(n.getNextSibling(), m.parent);
}
@Override
public void visit(Element n) {
List<Integer> cs = new ArrayList<Integer>();
contents.put(n, cs); // contents added later
SequenceNode content = new SequenceNode(cs, n.getOrigin());
xg.addNode(content);
dk.brics.xmlgraph.Node sn = content;
if (wrapchoices)
sn = wrapChoice(sn);
dk.brics.xmlgraph.Node en = new ElementNode(Automaton.makeString(n.getExpandedName()),
sn.getIndex(), false, n.getOrigin());
xg.addNode(en);
if (wrapchoices)
en = wrapChoice(en);
m.parent.addContent(en.getIndex());
push(n.getNextSibling(), m.parent);
push(n.getFirstAttr(), content);
push(n.getFirstChild(), content);
}
@Override
public void visit(TemplateGap n) {
if (gaps != GapConversion.IGNORE) {
dk.brics.xmlgraph.Node cn = new ChoiceNode(n.getGap(), gaps == GapConversion.OPEN, false, Collections.<Integer>emptyList(), n.getOrigin());
xg.addNode(cn);
m.parent.addContent(cn.getIndex());
tgaps.add(n.getGap());
addGapType(n.getGap(), n.getType(), n.getOrigin());
}
push(n.getNextSibling(), m.parent);
}
@Override
public void visit(Attribute n) {
dk.brics.xmlgraph.Node vn = new TextNode(Automaton.makeString(n.getValue()), n.getOrigin());
xg.addNode(vn);
if (wrapchoices)
vn = wrapChoice(vn);
dk.brics.xmlgraph.Node an = new AttributeNode(Automaton.makeString(n.getExpandedName()),
vn.getIndex(), n.getOrigin());
xg.addNode(an);
if (wrapchoices)
an = wrapChoice(an);
m.parent.addContent(an.getIndex());
push(n.getNextAttr(), m.parent);
}
@Override
public void visit(AttributeGap n) {
if (gaps != GapConversion.IGNORE) {
dk.brics.xmlgraph.Node cn = new ChoiceNode(n.getGap(), gaps == GapConversion.OPEN, false, Collections.<Integer>emptyList(), n.getOrigin());
xg.addNode(cn);
dk.brics.xmlgraph.Node an = new AttributeNode(Automaton.makeString(n.getExpandedName()),
cn.getIndex(), n.getOrigin());
xg.addNode(an);
if (wrapchoices)
an = wrapChoice(an);
m.parent.addContent(an.getIndex());
agaps.add(n.getGap());
addGapType(n.getGap(), n.getType(), n.getOrigin());
}
push(n.getNextAttr(), m.parent);
}
@Override
public void visit(ProcessingInstruction n) {
push(n.getNextSibling(), m.parent);
}
@Override
public void visit(Comment n) {
push(n.getNextSibling(), m.parent);
}
@Override
public void visit(NamespaceDecl n) {
}
private void addGapType(String gap, String type, Origin origin) {
if (type != null) {
String t = gap_types.get(gap);
if (t == null)
gap_types.put(gap, type);
else if (!t.equals(type))
throw new XMLException("type mismatch for gap " + gap, origin);
}
}
});
}
return new XMLGraphFragment(top, tgaps, agaps, gap_types);
}
}