001: /*
002: * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007.
003: *
004: * Licensed under the Aduna BSD-style license.
005: */
006: package org.openrdf.rio.rdfxml;
007:
008: import java.io.IOException;
009: import java.io.OutputStream;
010: import java.io.OutputStreamWriter;
011: import java.io.Writer;
012: import java.nio.charset.Charset;
013: import java.util.LinkedHashMap;
014: import java.util.Map;
015:
016: import info.aduna.xml.XMLUtil;
017:
018: import org.openrdf.model.BNode;
019: import org.openrdf.model.Literal;
020: import org.openrdf.model.Resource;
021: import org.openrdf.model.Statement;
022: import org.openrdf.model.URI;
023: import org.openrdf.model.Value;
024: import org.openrdf.model.vocabulary.RDF;
025: import org.openrdf.rio.RDFFormat;
026: import org.openrdf.rio.RDFHandlerException;
027: import org.openrdf.rio.RDFWriter;
028:
029: /**
030: * An implementation of the RDFWriter interface that writes RDF documents in
031: * XML-serialized RDF format.
032: */
033: public class RDFXMLWriter implements RDFWriter {
034:
035: /*-----------*
036: * Variables *
037: *-----------*/
038:
039: protected Writer writer;
040:
041: protected Map<String, String> namespaceTable;
042:
043: protected boolean writingStarted;
044:
045: protected boolean headerWritten;
046:
047: protected Resource lastWrittenSubject;
048:
049: /*--------------*
050: * Constructors *
051: *--------------*/
052:
053: /**
054: * Creates a new RDFXMLWriter that will write to the supplied OutputStream.
055: *
056: * @param out
057: * The OutputStream to write the RDF/XML document to.
058: */
059: public RDFXMLWriter(OutputStream out) {
060: this (new OutputStreamWriter(out, Charset.forName("UTF-8")));
061: }
062:
063: /**
064: * Creates a new RDFXMLWriter that will write to the supplied Writer.
065: *
066: * @param writer
067: * The Writer to write the RDF/XML document to.
068: */
069: public RDFXMLWriter(Writer writer) {
070: this .writer = writer;
071: namespaceTable = new LinkedHashMap<String, String>();
072: writingStarted = false;
073: headerWritten = false;
074: lastWrittenSubject = null;
075: }
076:
077: /*---------*
078: * Methods *
079: *---------*/
080:
081: public RDFFormat getRDFFormat() {
082: return RDFFormat.RDFXML;
083: }
084:
085: public void startRDF() {
086: if (writingStarted) {
087: throw new RuntimeException(
088: "Document writing has already started");
089: }
090: writingStarted = true;
091: }
092:
093: protected void writeHeader() throws IOException {
094: try {
095: // This export format needs the RDF namespace to be defined, add a
096: // prefix for it if there isn't one yet.
097: setNamespace("rdf", RDF.NAMESPACE, false);
098:
099: writer
100: .write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
101:
102: writeStartOfStartTag(RDF.NAMESPACE, "RDF");
103:
104: for (Map.Entry<String, String> entry : namespaceTable
105: .entrySet()) {
106: String name = entry.getKey();
107: String prefix = entry.getValue();
108:
109: writeNewLine();
110: writeIndent();
111: writer.write("xmlns");
112: if (prefix.length() > 0) {
113: writer.write(':');
114: writer.write(prefix);
115: }
116: writer.write("=\"");
117: writer.write(XMLUtil.escapeDoubleQuotedAttValue(name));
118: writer.write("\"");
119: }
120:
121: writeEndOfStartTag();
122:
123: writeNewLine();
124: } finally {
125: headerWritten = true;
126: }
127: }
128:
129: public void endRDF() throws RDFHandlerException {
130: if (!writingStarted) {
131: throw new RuntimeException(
132: "Document writing has not yet started");
133: }
134:
135: try {
136: if (!headerWritten) {
137: writeHeader();
138: }
139:
140: flushPendingStatements();
141:
142: writeNewLine();
143: writeEndTag(RDF.NAMESPACE, "RDF");
144:
145: writer.flush();
146: } catch (IOException e) {
147: throw new RDFHandlerException(e);
148: } finally {
149: writingStarted = false;
150: headerWritten = false;
151: }
152: }
153:
154: public void handleNamespace(String prefix, String name) {
155: setNamespace(prefix, name, false);
156: }
157:
158: protected void setNamespace(String prefix, String name,
159: boolean fixedPrefix) {
160: if (headerWritten) {
161: // Header containing namespace declarations has already been written
162: return;
163: }
164:
165: if (!namespaceTable.containsKey(name)) {
166: // Namespace not yet mapped to a prefix, try to give it the specified
167: // prefix
168:
169: boolean isLegalPrefix = prefix.length() == 0
170: || XMLUtil.isNCName(prefix);
171:
172: if (!isLegalPrefix || namespaceTable.containsValue(prefix)) {
173: // Specified prefix is not legal or the prefix is already in use,
174: // generate a legal unique prefix
175:
176: if (fixedPrefix) {
177: if (isLegalPrefix) {
178: throw new IllegalArgumentException(
179: "Prefix is already in use: " + prefix);
180: } else {
181: throw new IllegalArgumentException(
182: "Prefix is not a valid XML namespace prefix: "
183: + prefix);
184: }
185: }
186:
187: if (prefix.length() == 0 || !isLegalPrefix) {
188: prefix = "ns";
189: }
190:
191: int number = 1;
192:
193: while (namespaceTable.containsValue(prefix + number)) {
194: number++;
195: }
196:
197: prefix += number;
198: }
199:
200: namespaceTable.put(name, prefix);
201: }
202: }
203:
204: public void handleStatement(Statement st)
205: throws RDFHandlerException {
206: if (!writingStarted) {
207: throw new RuntimeException(
208: "Document writing has not yet been started");
209: }
210:
211: Resource subj = st.getSubject();
212: URI pred = st.getPredicate();
213: Value obj = st.getObject();
214:
215: // Verify that an XML namespace-qualified name can be created for the
216: // predicate
217: String predString = pred.toString();
218: int predSplitIdx = XMLUtil.findURISplitIndex(predString);
219: if (predSplitIdx == -1) {
220: throw new RDFHandlerException(
221: "Unable to create XML namespace-qualified name for predicate: "
222: + predString);
223: }
224:
225: String predNamespace = predString.substring(0, predSplitIdx);
226: String predLocalName = predString.substring(predSplitIdx);
227:
228: try {
229: if (!headerWritten) {
230: writeHeader();
231: }
232:
233: // SUBJECT
234: if (!subj.equals(lastWrittenSubject)) {
235: flushPendingStatements();
236:
237: // Write new subject:
238: writeNewLine();
239: writeStartOfStartTag(RDF.NAMESPACE, "Description");
240: if (subj instanceof BNode) {
241: BNode bNode = (BNode) subj;
242: writeAttribute(RDF.NAMESPACE, "nodeID", bNode
243: .getID());
244: } else {
245: URI uri = (URI) subj;
246: writeAttribute(RDF.NAMESPACE, "about", uri
247: .toString());
248: }
249: writeEndOfStartTag();
250: writeNewLine();
251:
252: lastWrittenSubject = subj;
253: }
254:
255: // PREDICATE
256: writeIndent();
257: writeStartOfStartTag(predNamespace, predLocalName);
258:
259: // OBJECT
260: if (obj instanceof Resource) {
261: Resource objRes = (Resource) obj;
262:
263: if (objRes instanceof BNode) {
264: BNode bNode = (BNode) objRes;
265: writeAttribute(RDF.NAMESPACE, "nodeID", bNode
266: .getID());
267: } else {
268: URI uri = (URI) objRes;
269: writeAttribute(RDF.NAMESPACE, "resource", uri
270: .toString());
271: }
272:
273: writeEndOfEmptyTag();
274: } else if (obj instanceof Literal) {
275: Literal objLit = (Literal) obj;
276:
277: // language attribute
278: if (objLit.getLanguage() != null) {
279: writeAttribute("xml:lang", objLit.getLanguage());
280: }
281:
282: // datatype attribute
283: boolean isXMLLiteral = false;
284: URI datatype = objLit.getDatatype();
285: if (datatype != null) {
286: // Check if datatype is rdf:XMLLiteral
287: isXMLLiteral = datatype.equals(RDF.XMLLITERAL);
288:
289: if (isXMLLiteral) {
290: writeAttribute(RDF.NAMESPACE, "parseType",
291: "Literal");
292: } else {
293: writeAttribute(RDF.NAMESPACE, "datatype",
294: datatype.toString());
295: }
296: }
297:
298: writeEndOfStartTag();
299:
300: // label
301: if (isXMLLiteral) {
302: // Write XML literal as plain XML
303: writer.write(objLit.getLabel());
304: } else {
305: writeCharacterData(objLit.getLabel());
306: }
307:
308: writeEndTag(predNamespace, predLocalName);
309: }
310:
311: writeNewLine();
312:
313: // Don't write </rdf:Description> yet, maybe the next statement
314: // has the same subject.
315: } catch (IOException e) {
316: throw new RDFHandlerException(e);
317: }
318: }
319:
320: public void handleComment(String comment)
321: throws RDFHandlerException {
322: try {
323: if (!headerWritten) {
324: writeHeader();
325: }
326:
327: flushPendingStatements();
328:
329: writer.write("<!-- ");
330: writer.write(comment);
331: writer.write(" -->");
332: writeNewLine();
333: } catch (IOException e) {
334: throw new RDFHandlerException(e);
335: }
336: }
337:
338: protected void flushPendingStatements() throws IOException {
339: if (lastWrittenSubject != null) {
340: // The last statement still has to be closed:
341: writeEndTag(RDF.NAMESPACE, "Description");
342: writeNewLine();
343:
344: lastWrittenSubject = null;
345: }
346: }
347:
348: protected void writeStartOfStartTag(String namespace,
349: String localName) throws IOException {
350: String prefix = namespaceTable.get(namespace);
351:
352: if (prefix == null) {
353: writer.write("<");
354: writer.write(localName);
355: writer.write(" xmlns=\"");
356: writer.write(XMLUtil.escapeDoubleQuotedAttValue(namespace));
357: writer.write("\"");
358: } else if (prefix.length() == 0) {
359: // default namespace
360: writer.write("<");
361: writer.write(localName);
362: } else {
363: writer.write("<");
364: writer.write(prefix);
365: writer.write(":");
366: writer.write(localName);
367: }
368: }
369:
370: protected void writeAttribute(String attName, String value)
371: throws IOException {
372: writer.write(" ");
373: writer.write(attName);
374: writer.write("=\"");
375: writer.write(XMLUtil.escapeDoubleQuotedAttValue(value));
376: writer.write("\"");
377: }
378:
379: protected void writeAttribute(String namespace, String attName,
380: String value) throws IOException {
381: String prefix = namespaceTable.get(namespace);
382:
383: if (prefix == null || prefix.length() == 0) {
384: throw new RuntimeException(
385: "No prefix has been declared for the namespace used in this attribute: "
386: + namespace);
387: }
388:
389: writer.write(" ");
390: writer.write(prefix);
391: writer.write(":");
392: writer.write(attName);
393: writer.write("=\"");
394: writer.write(XMLUtil.escapeDoubleQuotedAttValue(value));
395: writer.write("\"");
396: }
397:
398: protected void writeEndOfStartTag() throws IOException {
399: writer.write(">");
400: }
401:
402: protected void writeEndOfEmptyTag() throws IOException {
403: writer.write("/>");
404: }
405:
406: protected void writeEndTag(String namespace, String localName)
407: throws IOException {
408: String prefix = namespaceTable.get(namespace);
409:
410: if (prefix == null || prefix.length() == 0) {
411: writer.write("</");
412: writer.write(localName);
413: writer.write(">");
414: } else {
415: writer.write("</");
416: writer.write(prefix);
417: writer.write(":");
418: writer.write(localName);
419: writer.write(">");
420: }
421: }
422:
423: protected void writeCharacterData(String chars) throws IOException {
424: writer.write(XMLUtil.escapeCharacterData(chars));
425: }
426:
427: protected void writeIndent() throws IOException {
428: writer.write("\t");
429: }
430:
431: protected void writeNewLine() throws IOException {
432: writer.write("\n");
433: }
434: }
|