Skip to content

Commit 9084ca4

Browse files
committed
Add XmlIterParser and example whelktool script
Useful for loading larger RDF/XML files by iterating on each description. This is a rather idiosyncratic approach, but worth testing (with system librarians) for importing transformed XML.
1 parent 8d940a7 commit 9084ca4

2 files changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package whelk.converter;
2+
3+
import javax.xml.stream.XMLEventFactory;
4+
import javax.xml.stream.XMLEventReader;
5+
import javax.xml.stream.XMLEventWriter;
6+
import javax.xml.stream.XMLInputFactory;
7+
import javax.xml.stream.XMLOutputFactory;
8+
import javax.xml.stream.events.Namespace;
9+
import javax.xml.stream.events.StartElement;
10+
import java.io.InputStream;
11+
import java.io.StringWriter;
12+
import java.util.ArrayList;
13+
import java.util.Iterator;
14+
import java.util.List;
15+
import java.util.NoSuchElementException;
16+
17+
public class XmlIterParser implements Iterator<String>, AutoCloseable {
18+
19+
private final XMLEventReader reader;
20+
private final XMLOutputFactory outputFactory;
21+
private final XMLEventFactory eventFactory;
22+
23+
private final List<Namespace> ancestorNamespaces = new ArrayList<>();
24+
private StartElement childStart = null;
25+
26+
public XmlIterParser(InputStream is) throws Exception {
27+
var inputFactory = XMLInputFactory.newInstance();
28+
// Enable DTD entity support (fairly common in RDF/XML)
29+
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, true);
30+
// But no external (insecure!)
31+
inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
32+
33+
this.reader = inputFactory.createXMLEventReader(is);
34+
this.outputFactory = XMLOutputFactory.newInstance();
35+
this.eventFactory = XMLEventFactory.newInstance();
36+
}
37+
38+
@Override
39+
public boolean hasNext() {
40+
if (childStart == null) {
41+
try {
42+
while (reader.hasNext()) {
43+
var event = reader.nextEvent();
44+
// Ignore root element
45+
if (event.isStartElement()) {
46+
reader.nextEvent();
47+
var start = event.asStartElement();
48+
// Collect active namespaces
49+
var nsIter = start.getNamespaces();
50+
while (nsIter.hasNext()) {
51+
ancestorNamespaces.add((Namespace) nsIter.next());
52+
}
53+
break;
54+
}
55+
}
56+
} catch (Exception e) {
57+
throw new RuntimeException(e);
58+
}
59+
}
60+
61+
if (!reader.hasNext()) {
62+
return false;
63+
}
64+
65+
try {
66+
while (reader.hasNext()) {
67+
var event = reader.peek();
68+
if (event.isStartElement()) {
69+
childStart = event.asStartElement();
70+
return true;
71+
}
72+
reader.nextEvent(); // Drop intermediate whitespace
73+
}
74+
return false;
75+
} catch (Exception e) {
76+
throw new RuntimeException(e);
77+
}
78+
}
79+
80+
private StartElement completeChildStart(StartElement childStart) {
81+
var allActiveNamespaces = new ArrayList<>(ancestorNamespaces);
82+
var childNsIter = childStart.getNamespaces();
83+
while (childNsIter.hasNext()) {
84+
var ns = (Namespace) childNsIter.next();
85+
// Override ancestor duplicates with local declarations
86+
allActiveNamespaces.removeIf(n -> n.getPrefix().equals(ns.getPrefix()));
87+
allActiveNamespaces.add(ns);
88+
}
89+
90+
return eventFactory.createStartElement(
91+
childStart.getName(),
92+
childStart.getAttributes(),
93+
allActiveNamespaces.iterator()
94+
);
95+
}
96+
97+
@Override
98+
public String next() {
99+
if (!hasNext()) {
100+
throw new NoSuchElementException();
101+
}
102+
103+
var sw = new StringWriter();
104+
XMLEventWriter writer;
105+
try {
106+
writer = outputFactory.createXMLEventWriter(sw);
107+
} catch (Exception e) {
108+
throw new RuntimeException(e);
109+
}
110+
111+
if (childStart == null) {
112+
throw new NoSuchElementException();
113+
}
114+
115+
try {
116+
reader.nextEvent(); // consume
117+
childStart = completeChildStart(childStart);
118+
writer.add(childStart);
119+
120+
var depth = 1;
121+
while (reader.hasNext() && depth > 0) {
122+
var event = reader.nextEvent();
123+
writer.add(event);
124+
125+
if (event.isStartElement()) depth++;
126+
if (event.isEndElement()) depth--;
127+
}
128+
129+
writer.close();
130+
return sw.toString();
131+
132+
} catch (Exception e) {
133+
throw new RuntimeException("Collecting inner XML failed", e);
134+
}
135+
}
136+
137+
@Override
138+
public void close() throws Exception {
139+
if (reader != null) reader.close();
140+
}
141+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import java.util.stream.StreamSupport
2+
3+
import whelk.converter.XmlIterParser
4+
import whelk.converter.RdfXmlToJsonLdParser
5+
6+
import whelk.Whelk
7+
import whelk.datatool.DocumentItem
8+
import static whelk.converter.RdfReader.readRdf
9+
10+
11+
DocumentItem saveRecord(Map data) {
12+
Map record = data[GRAPH].find { it -> it[TYPE] == 'Record' }
13+
assert record
14+
def mainId = record['mainEntity'][ID]
15+
assert mainId.endsWith('#it')
16+
Map mainEntity = data[GRAPH].find { it -> it[ID] == mainId }
17+
assert mainEntity
18+
19+
return create(['@graph': [record, mainEntity]])
20+
}
21+
22+
var rdfSourcePath = System.getProperty("rdfdata")
23+
var file = new File(rdfSourcePath)
24+
25+
var whelk = getWhelk()
26+
var context = whelk.storage.loadDocumentByMainId(whelk.systemContextUri, null).data
27+
28+
file.withInputStream {
29+
var iterParser = new XmlIterParser(it)
30+
31+
for (var xml in iterParser) {
32+
var bis = new ByteArrayInputStream(xml.getBytes('UTF-8'))
33+
var data = RdfXmlToJsonLdParser.parse(bis, context)
34+
var docItem = saveRecord(data)
35+
selectFromIterable([docItem], { newItem ->
36+
newItem.scheduleSave()
37+
})
38+
}
39+
}

0 commit comments

Comments
 (0)