Skip to content

Commit 81141f6

Browse files
committed
Add an operation, ReattachNamedEdge, which reattaches a named edge to a new gov and/or dep
1 parent c1ed2de commit 81141f6

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package edu.stanford.nlp.semgraph.semgrex.ssurgeon;
2+
3+
import java.io.StringWriter;
4+
5+
import edu.stanford.nlp.ling.IndexedWord;
6+
import edu.stanford.nlp.semgraph.semgrex.SemgrexMatcher;
7+
import edu.stanford.nlp.semgraph.SemanticGraph;
8+
import edu.stanford.nlp.semgraph.SemanticGraphEdge;
9+
10+
/**
11+
* Given a named edge, reconnect that edge elsewhere in the graph, changing either the gov and/or dep.
12+
*<br>
13+
* If an edge already exists with the new named relation,
14+
* <i>that</i> edge is deleted permanently. That way, named
15+
* references to the first edge should still work.
16+
*
17+
* @author John Bauer
18+
*
19+
*/
20+
public class ReattachNamedEdge extends SsurgeonEdit {
21+
public static final String LABEL = "reattachNamedEdge";
22+
23+
protected final String edgeName; // Name of the matched edge in the SemgrexPattern
24+
protected final String govNodeName; // Where to put the new gov. If null, will not edit
25+
protected final String depNodeName; // Where to put the new dep. If null, will not edit
26+
27+
public ReattachNamedEdge(String edgeName, String gov, String dep) {
28+
if (edgeName == null) {
29+
throw new SsurgeonParseException("ReattachNamedEdge created with no edge name!");
30+
}
31+
if (gov == null && dep == null) {
32+
throw new SsurgeonParseException("ReattachNamedEdge created with both gov and dep missing!");
33+
}
34+
35+
this.edgeName = edgeName;
36+
this.govNodeName = gov;
37+
this.depNodeName = dep;
38+
}
39+
40+
@Override
41+
public String toEditString() {
42+
StringWriter buf = new StringWriter();
43+
buf.write(LABEL); buf.write("\t");
44+
45+
buf.write(Ssurgeon.EDGE_NAME_ARG);buf.write(" ");
46+
buf.write(edgeName);
47+
48+
if (govNodeName != null) {
49+
buf.write("\t");
50+
buf.write(Ssurgeon.GOV_NODENAME_ARG);buf.write(" ");
51+
buf.write(govNodeName);
52+
}
53+
if (depNodeName != null) {
54+
buf.write("\t");
55+
buf.write(Ssurgeon.DEP_NODENAME_ARG);buf.write(" ");
56+
buf.write(depNodeName);
57+
}
58+
59+
return buf.toString();
60+
}
61+
62+
/**
63+
* "Reattach" the named edge by removing it and then recreating it with the new gov and/or dep
64+
*/
65+
@Override
66+
public boolean evaluate(SemanticGraph sg, SemgrexMatcher sm) {
67+
SemanticGraphEdge edge = sm.getEdge(edgeName);
68+
69+
if (edge != null) {
70+
final IndexedWord gov = (govNodeName != null) ? sm.getNode(govNodeName) : edge.getSource();
71+
final IndexedWord dep = (depNodeName != null) ? sm.getNode(depNodeName) : edge.getTarget();
72+
if (gov == edge.getSource() && dep == edge.getTarget()) {
73+
// we were asked to point the edge to the same nodes it already pointed to
74+
// nothing to do
75+
return false;
76+
}
77+
boolean success = sg.removeEdge(edge);
78+
if (!success) {
79+
// maybe it was already removed somehow by a previous operation
80+
return false;
81+
}
82+
final SemanticGraphEdge newEdge;
83+
found: {
84+
for (SemanticGraphEdge existingEdge : sg.getAllEdges(edge.getSource(), edge.getTarget())) {
85+
if (existingEdge.getRelation().equals(edge.getRelation())) {
86+
newEdge = existingEdge;
87+
break found;
88+
}
89+
}
90+
newEdge = new SemanticGraphEdge(gov,
91+
dep,
92+
edge.getRelation(),
93+
edge.getWeight(),
94+
edge.isExtra());
95+
sg.addEdge(newEdge);
96+
}
97+
// whether we recreated a new edge with the new relation,
98+
// or found an existing edge with the relation we wanted,
99+
// update the named edge in the SemgrexMatcher so future
100+
// iterations have the name connected to the edge
101+
// TODO: if an existing edge was clobbered, perhaps we need to
102+
// update anything that named it
103+
sm.putNamedEdge(edgeName, newEdge);
104+
return true;
105+
}
106+
return false;
107+
}
108+
}

src/edu/stanford/nlp/semgraph/semgrex/ssurgeon/Ssurgeon.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
* <li> {@code relabelNamedEdge -edge edgename -reln depType}
7979
* <li> {@code removeEdge -gov node1 -dep node2 reln depType}
8080
* <li> {@code removeNamedEdge -edge edgename}
81+
* <li> {@code reattachNamedEdge -edge edgename -gov gov -dep dep}
8182
* <li> {@code addDep -gov node1 -reln depType -position where ...attributes...}
8283
* <li> {@code editNode -node node ...attributes...}
8384
* <li> {@code setRoots n1 (n2 n3 ...)}
@@ -105,6 +106,12 @@
105106
* {@code removeNamedEdge} deletes an edge based on its name.
106107
* {@code edge} is the name of the edge in the Semgrex pattern.
107108
*</p><p>
109+
* {@code reattachNamedEdge} changes an edge's gov and/or dep based on its name.
110+
* {@code edge} is the name of the edge in the Semgrex pattern.
111+
* {@code -gov} is the governor to attach to, a named node from the Semgrex pattern. If left blank, no edit.
112+
* {@code -dep} is the dependent to attach to, a named node from the Semgrex pattern. If left blank, no edit.
113+
* At least one of {@code -gov} or {@code -dep} must be set.
114+
*</p><p>
108115
* {@code addDep} adds a word and a dependency arc to the dependency graph.
109116
* {@code -gov} is the governor to attach to, a named node from the Semgrex pattern.
110117
* {@code -reln} is the name of the dependency type to use.
@@ -510,6 +517,8 @@ public static SsurgeonEdit parseEditLine(String editLine, Map<String, String> at
510517
}
511518
GrammaticalRelation reln = GrammaticalRelation.valueOf(language, argsBox.reln);
512519
return new AddEdge(argsBox.govNodeName, argsBox.dep, reln, argsBox.weight);
520+
} else if (command.equalsIgnoreCase(ReattachNamedEdge.LABEL)) {
521+
return new ReattachNamedEdge(argsBox.edge, argsBox.govNodeName, argsBox.dep);
513522
} else if (command.equalsIgnoreCase(DeleteGraphFromNode.LABEL)) {
514523
return new DeleteGraphFromNode(argsBox.node);
515524
} else if (command.equalsIgnoreCase(EditNode.LABEL)) {

test/src/edu/stanford/nlp/semgraph/semgrex/ssurgeon/SsurgeonTest.java

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,110 @@ public void forbidIllegalAttributes() {
13221322
}
13231323
}
13241324

1325+
/**
1326+
* Test a two step process to reattach an edge elsewhere
1327+
*<br>
1328+
* Uses a real example from UD_English-Pronouns
1329+
*/
1330+
@Test
1331+
public void readXMLTwoStepReattach() {
1332+
String doc = String.join(newline,
1333+
"<ssurgeon-pattern-list>",
1334+
" <ssurgeon-pattern>",
1335+
" <uid>38</uid>",
1336+
" <notes>This tests the two step process of reattaching an edge</notes>",
1337+
" <language>UniversalEnglish</language>",
1338+
" <semgrex>" + XMLUtils.escapeXML("{word:/[.]/}=punct <punct=bad {}=parent << {$}=root : {}=parent << {}=root") + "</semgrex>",
1339+
" <edit-list>removeNamedEdge -edge bad</edit-list>",
1340+
" <edit-list>addEdge -gov root -dep punct -reln punct</edit-list>",
1341+
" </ssurgeon-pattern>",
1342+
"</ssurgeon-pattern-list>");
1343+
Ssurgeon inst = Ssurgeon.inst();
1344+
List<SsurgeonPattern> patterns = inst.readFromString(doc);
1345+
assertEquals(patterns.size(), 1);
1346+
SsurgeonPattern pattern = patterns.get(0);
1347+
1348+
// check a simple case of relabeling
1349+
SemanticGraph sg = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 csubj> [clean-5 mark> to-4 punct> .-6]]");
1350+
SemanticGraph expected = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 punct> .-6 csubj> [clean-5 mark> to-4]]");
1351+
SemanticGraph newSg = pattern.iterate(sg);
1352+
assertEquals(newSg, expected);
1353+
}
1354+
1355+
/**
1356+
* Test reattachNamedEdge, which is a one step version of reattaching where an edge goes
1357+
*<br>
1358+
* Uses a real example from UD_English-Pronouns
1359+
*/
1360+
@Test
1361+
public void readXMLOneStepReattach() {
1362+
String doc = String.join(newline,
1363+
"<ssurgeon-pattern-list>",
1364+
" <ssurgeon-pattern>",
1365+
" <uid>38</uid>",
1366+
" <notes>This tests the two step process of reattaching an edge</notes>",
1367+
" <language>UniversalEnglish</language>",
1368+
" <semgrex>" + XMLUtils.escapeXML("{word:/[.]/}=punct <punct=bad {}=parent << {$}=root : {}=parent << {}=root") + "</semgrex>",
1369+
" <edit-list>reattachNamedEdge -edge bad -gov root</edit-list>",
1370+
" </ssurgeon-pattern>",
1371+
"</ssurgeon-pattern-list>");
1372+
Ssurgeon inst = Ssurgeon.inst();
1373+
List<SsurgeonPattern> patterns = inst.readFromString(doc);
1374+
assertEquals(patterns.size(), 1);
1375+
SsurgeonPattern pattern = patterns.get(0);
1376+
1377+
// check a simple case of relabeling
1378+
SemanticGraph sg = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 csubj> [clean-5 mark> to-4 punct> .-6]]");
1379+
SemanticGraph expected = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 punct> .-6 csubj> [clean-5 mark> to-4]]");
1380+
SemanticGraph newSg = pattern.iterate(sg);
1381+
assertEquals(newSg, expected);
1382+
1383+
// this tests -gov and -dep both set
1384+
doc = String.join(newline,
1385+
"<ssurgeon-pattern-list>",
1386+
" <ssurgeon-pattern>",
1387+
" <uid>38</uid>",
1388+
" <notes>This tests the two step process of reattaching an edge</notes>",
1389+
" <language>UniversalEnglish</language>",
1390+
" <semgrex>" + XMLUtils.escapeXML("{word:/[.]/}=punct <punct=bad {}=parent << {$}=root : {}=parent << {}=root") + "</semgrex>",
1391+
" <edit-list>reattachNamedEdge -edge bad -gov root -dep punct</edit-list>",
1392+
" </ssurgeon-pattern>",
1393+
"</ssurgeon-pattern-list>");
1394+
inst = Ssurgeon.inst();
1395+
patterns = inst.readFromString(doc);
1396+
assertEquals(patterns.size(), 1);
1397+
pattern = patterns.get(0);
1398+
1399+
// check a simple case of relabeling, this time with the (unnecessary) -dep specifier as well
1400+
sg = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 csubj> [clean-5 mark> to-4 punct> .-6]]");
1401+
expected = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 punct> .-6 csubj> [clean-5 mark> to-4]]");
1402+
newSg = pattern.iterate(sg);
1403+
assertEquals(newSg, expected);
1404+
1405+
// this tests -dep set by itself (although the operation itself is nonsense)
1406+
doc = String.join(newline,
1407+
"<ssurgeon-pattern-list>",
1408+
" <ssurgeon-pattern>",
1409+
" <uid>38</uid>",
1410+
" <notes>This tests the two step process of reattaching an edge</notes>",
1411+
" <language>UniversalEnglish</language>",
1412+
" <semgrex>" + XMLUtils.escapeXML("{$}=root >csubj=foo ({word:clean}=n1 >mark=bar {}=n2)") + "</semgrex>",
1413+
" <edit-list>reattachNamedEdge -edge foo -dep n2</edit-list>",
1414+
" <edit-list>reattachNamedEdge -edge bar -gov n2 -dep n1</edit-list>",
1415+
" </ssurgeon-pattern>",
1416+
"</ssurgeon-pattern-list>");
1417+
inst = Ssurgeon.inst();
1418+
patterns = inst.readFromString(doc);
1419+
assertEquals(patterns.size(), 1);
1420+
pattern = patterns.get(0);
1421+
1422+
// do some random rearranging to force a test of reattachNamedEdge with -dep set
1423+
sg = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 csubj> [clean-5 mark> to-4 punct> .-6]]");
1424+
expected = SemanticGraph.valueOf("[easy-3 nsubj> Hers-1 cop> is-2 csubj> [to-4 mark> [clean-5 punct> .-6]]]");
1425+
newSg = pattern.iterate(sg);
1426+
assertEquals(newSg, expected);
1427+
}
1428+
13251429
/**
13261430
* Simple test of an Ssurgeon edit script. This instances a simple semantic graph,
13271431
* a semgrex pattern, and then the resulting actions over the named nodes in the

0 commit comments

Comments
 (0)