}
return rootelements;
}
public XMLGraph transferGet(final GetStm s, final XMLGraph base) {
final XMLGraph g = base.clone();
if (g.isUnknown()) {
return g;
}
ChoiceNode cn = stm_nodes.getGetChoiceNode(s, g);
final LinkedHashSet<Integer> c = new LinkedHashSet<Integer>();
cn.setContent(c, g);
switch (s.getKind()) {
case GET:
case GETELEMENT: // TODO: to improve analysis precision, only select the *first* element for getElement
case GETELEMENTS: {
StatusMap sm;
// try {
// sm = evaluator.evaluate(base, s.getXPath(), dummy_root, dummy_root_content);
// } catch (XPathException e) {
// throw new XMLXPathException(e);
// }
sm = evaluateXPathOrRoot(s.getXPath(), base);
checkXPathEmpty(s, base, sm);
for (int i : sm.getNodes()) {
if (g.getNode(i) instanceof ConcreteNode) {
StatusMap.Status status = sm.get(i);
if (status!=StatusMap.Status.NONE && status!=StatusMap.Status.NEVER)
c.add(i);
}
}
c.addAll(cn.getContents());
g.getRoots().clear();
g.addRoot(cn);
// TODO: could improve precision by considering simple XPath *predicates*
break;
}
case GETFIRSTATTR:
case GETFIRSTATTRIBUTE:
case GETFIRSTCHILD:
g.getRoots().clear();
g.addRoot(cn);
Set<ElementNode> rootElements = getFirstRootElements(base);
for (ElementNode node : rootElements) {
g.getNode(node.getContent()).process(new CachedNodeProcessor<EPresence>() {
@Override
public EPresence cycle() {
return EPresence.UNKNOWN;
}
@Override
public EPresence process(AttributeNode n) {
c.add(n.getIndex());
return EPresence.NONEMPTY;
}
@Override
public EPresence process(ElementNode n) {
if (s.getKind() == Kind.GETFIRSTATTR || s.getKind() == Kind.GETFIRSTATTRIBUTE) {
return EPresence.EMPTY;
} else {
c.add(n.getIndex());
return EPresence.NONEMPTY;
}
}
@Override
public EPresence process(ChoiceNode n) {
if (n.isGap() && n.isOpen() && s.getKind() == Kind.GETFIRSTATTR && g.getOpenAttributeGaps().contains(n.getName())) {
c.add(n.getIndex());
return EPresence.UNKNOWN;
} else {
EPresence result = EPresence.BOTTOM;
for (int child : n.getContents()) {
result = result.leastUpperBound(g.getNode(child).process(this));
}
return result;
}
}
@Override
public EPresence process(SequenceNode n) {
EPresence result = EPresence.EMPTY;
for (int child : n.getContents()) {
result = result.concat(g.getNode(child).process(this));
if (result.definitelyNonEmpty())
break;
}
return result;
}
@Override
public EPresence process(OneOrMoreNode n) {
return g.getNode(n.getContent()).process(this);
}
@Override
public EPresence process(TextNode n) {
return EPresence.EMPTY;
}
@Override
public EPresence process(InterleaveNode n) {
EPresence result = EPresence.BOTTOM;
for (int child : n.getContents()) {
result = result.leastUpperBound(g.getNode(child).process(this));
}
return result;
}
});
}
break;
case GETFIRSTELEMENT: {
final Emptiness emptiness = new Emptiness(g, EPresence.EMPTY, EPresence.NONEMPTY, EPresence.EMPTY, EPresence.EMPTY);
final FirstRootAnalysis first = new FirstRootAnalysis(g, emptiness, false);
final RootAnalysis roots = new RootAnalysis(g);
final int emptyIndex = stm_nodes.getGetEmptySequence(s, g).getIndex();
// we model this as "replace every root node that is not the first root element with the empty sequence"
g.processReachableNodes(new NodeProcessor<Object>() {
@Override
public Object process(ChoiceNode n) {
EBooleanLattice r = roots.get(n.getIndex());
if (r.definitelyNot()) {
return this;
}
LinkedList<Integer> removed = new LinkedList<Integer>();
boolean addempty = false;
for (int child : n.getContents()) {
if (roots.get(child).maybe()) {
if (r.definitely()) {
if (first.get(child).definitelyNot() || emptiness.get(child).definitelyEmpty()) {
removed.add(child);
}
}
if (first.get(child).maybeNot() && emptiness.get(child).maybeNonEmpty()) {
addempty = true;
}
}
}
boolean removegap = false;
if (n.isGap() && n.isOpen() && emptiness.get(n.getIndex()).definitelyEmpty() && r.definitely()) {
removegap = true;
}
if (!removed.isEmpty() || addempty || removegap) {
LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(n.getContents());
cs.removeAll(removed);
cs.add(emptyIndex);
if (removegap) {
n.setContentAndStatus(false, true, cs, g);
} else {
n.setContent(cs, g);
}
}
return this;
}
});
LinkedList<Integer> removed = new LinkedList<Integer>();
for (int child : g.getRoots()) {
switch (emptiness.get(child)) {
case EMPTY:
case BOTTOM:
// if no ELEMENT can occur as root here, then remove the root edge
removed.add(child);
break;
default: ;
}
}
g.getRoots().removeAll(removed);
// note: no need to add an empty root, because getFirstElement() throws
// an exception if none of the roots are elements
break;
}
case GETNEXTATTR:
case GETNEXTATTRIBUTE:
case GETNEXTSIBLING: {
Emptiness emptiness = new Emptiness(g,
null, // allow attributes
s.getKind() == Kind.GETNEXTSIBLING ? null : EPresence.BOTTOM, // allow elements
s.getKind() == Kind.GETNEXTSIBLING ? null : EPresence.BOTTOM, // allow text
s.getKind() != Kind.GETNEXTATTRIBUTE ? null : EPresence.BOTTOM); // allow gaps
final FirstRootAnalysis first = new FirstRootAnalysis(g, emptiness, s.getKind() == Kind.GETNEXTSIBLING);
final int emptyIndex = stm_nodes.getGetEmptySequence(s, g).getIndex();
// We model the "get next sibling" operations as "replace first root with empty sequece".
// If a ChoiceNode has an edge to a ConcreteNode that might be the first root,
// then the ChoiceNode gets an additional edge to the empty sequence.
// If that ConcreteNode can only occur as the first root, then the edge to it is removed.
g.processReachableNodes(new NodeProcessor<Object>() {
@Override
public Object process(ChoiceNode n) {
if (first.get(n.getIndex()).definitelyNot())
return this;
LinkedList<Integer> removed = new LinkedList<Integer>();
boolean addempty = false;
for (int child : n.getContents()) {
if (!(g.getNode(child) instanceof ConcreteNode))
continue;
switch (first.get(child)) {
case YES:
// removed.add(child); // cannot remove due to invisible comments and PIs :(
addempty = true;
break;
case MAYBE:
addempty = true;
break;
default: ;
}
}
if (removed.isEmpty() || addempty) {
LinkedHashSet<Integer> cs = new LinkedHashSet<Integer>(n.getContents());
cs.removeAll(removed);
cs.add(emptyIndex);
n.setContent(cs, g);
}
return removed;
}
});
LinkedList<Integer> removed = new LinkedList<Integer>();
boolean addempty = false;
for (int root : g.getRoots()) {
if (!(g.getNode(root) instanceof ConcreteNode))
continue;
switch (first.get(root)) {
case YES:
// removed.add(root); // cannot remove due to invisible comments and PIs :(
addempty = true;
break;
case MAYBE:
addempty = true;
break;
default: ;
}
}
g.getRoots().removeAll(removed);
if (addempty) {
g.getRoots().add(emptyIndex);
}
break;
}
default:
throw new RuntimeException("Unexpected GET kind: " + s.getKind());
}//switch
g.sharpen();
return g;
}