src/RDF_Writer.vala (10567B) - raw
1 /*
2 * xmpedit
3 * Copyright 2010 Dan Callaghan <djc@djc.id.au>
4 * Released under GPLv2
5 */
6
7 namespace RDF {
8
9 private class Writer {
10
11 private static Regex local_name_regex;
12 static construct {
13 try {
14 local_name_regex = new Regex(".*?([_a-zA-Z][-_.a-zA-Z0-9]*)$");
15 } catch (RegexError e) {
16 error(@"local_name_regex is broken: $(e.message)");
17 }
18 }
19
20 private Graph graph;
21 private Genx.Writer genx_writer = new Genx.Writer();
22 private StringBuilder output = new StringBuilder();
23 private Gee.Set<Statement> written_statements = new Gee.HashSet<Statement>();
24
25 public Writer(Graph graph) {
26 this.graph = graph;
27 }
28
29 public string get_xml() {
30 return output.str;
31 }
32
33 /**
34 * N.B. not a general RDF writer,
35 * only includes statements reachable from start_node!
36 */
37 public void write(URIRef start_node) {
38 genx_writer.start_doc(output);
39 unowned Genx.Namespace rdf_ns = genx_writer.declare_namespace(RDF_NS, "rdf");
40 unowned Genx.Element rdf_el = genx_writer.declare_element(rdf_ns, "RDF");
41
42 rdf_description_el = genx_writer.declare_element(rdf_ns, "Description");
43 rdf_about_attr = genx_writer.declare_attribute(rdf_ns, "about");
44 rdf_resource_attr = genx_writer.declare_attribute(rdf_ns, "resource");
45 unowned Genx.Namespace xml_ns = genx_writer.declare_namespace(XML_NS, "xml");
46 xml_lang_attr = genx_writer.declare_attribute(xml_ns, "lang");
47
48 rdf_el.start();
49 write_resource(start_node, true);
50 genx_writer.end_element();
51 genx_writer.end_document();
52 }
53
54 /* for efficiency */
55 private unowned Genx.Element rdf_description_el;
56 private unowned Genx.Attribute rdf_about_attr;
57 private unowned Genx.Attribute rdf_resource_attr;
58 private unowned Genx.Attribute xml_lang_attr;
59
60 private void write_resource(SubjectNode node, bool is_start = false) {
61 if (is_start) {
62 rdf_description_el.start();
63 rdf_about_attr.add("");
64 } else {
65 URIRef type = null;
66 foreach (var statement in graph.find_matching_statements(
67 node, new URIRef(RDF_NS + "type"), null)) {
68 if (statement.object is URIRef) {
69 type = (URIRef) statement.object;
70 written_statements.add(statement);
71 break;
72 }
73 }
74 if (type != null) {
75 unowned Genx.Namespace ns;
76 string local_name;
77 split_uri(((URIRef) type).uri, out ns, out local_name);
78 unowned Genx.Element resource_el; // XXX reuse
79 resource_el = genx_writer.declare_element(ns, local_name);
80 resource_el.start();
81 } else {
82 rdf_description_el.start();
83 }
84 if (node is URIRef) {
85 rdf_about_attr.add(((URIRef) node).uri);
86 }
87 }
88 foreach (var statement in graph.find_matching_statements(node, null, null)) {
89 if (!(statement in written_statements)) {
90 write_property(statement.predicate, statement.object);
91 written_statements.add(statement);
92 }
93 }
94 genx_writer.end_element();
95 }
96
97 private void write_property(URIRef predicate, Node object) {
98 unowned Genx.Namespace ns;
99 string local_name;
100 split_uri(predicate.uri, out ns, out local_name);
101 unowned Genx.Element property_el; // XXX reuse
102 property_el = genx_writer.declare_element(ns, local_name);
103 property_el.start();
104 if (object is PlainLiteral) {
105 PlainLiteral literal = (PlainLiteral) object;
106 if (literal.lang != null) {
107 xml_lang_attr.add(literal.lang);
108 }
109 genx_writer.add_text(literal.lexical_value);
110 } else if (object is URIRef) {
111 URIRef uriref = (URIRef) object;
112 if (graph.has_matching_statement(uriref, null, null)) {
113 write_resource(uriref);
114 } else {
115 rdf_resource_attr.add(uriref.uri);
116 }
117 } else if (object is Blank) {
118 write_resource((Blank) object);
119 } else {
120 critical(@"Unhandled object type: $(object)");
121 }
122 genx_writer.end_element();
123 }
124
125 private void split_uri(string uri,
126 out unowned Genx.Namespace ns, out string local_name) {
127 MatchInfo match_info;
128 local_name_regex.match(uri, 0, out match_info);
129 if (!match_info.matches())
130 error(@"Cannot match local name part of $(uri)");
131 local_name = match_info.fetch(1);
132 var ns_uri = uri.substring(0, uri.length - local_name.length);
133 var ns_prefix = is_wellknown_ns(ns_uri);
134 ns = genx_writer.declare_namespace(ns_uri, ns_prefix);
135 }
136
137 private string? is_wellknown_ns(string ns) {
138 for (int i = 0; i < WELL_KNOWN_NAMESPACES.length[0]; i ++)
139 if (WELL_KNOWN_NAMESPACES[i,1] == ns)
140 return WELL_KNOWN_NAMESPACES[i,0];
141 return null;
142 }
143
144 private static string[,] WELL_KNOWN_NAMESPACES = {
145 { "foaf", "http://xmlns.com/foaf/0.1/" },
146 { "dc", "http://purl.org/dc/elements/1.1/" },
147 { "Iptc4xmlCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" }
148 };
149
150 }
151
152 #if TEST
153
154 namespace WriterTests {
155
156 public void test_literal_object() {
157 var person = new URIRef("http://example.com/");
158 var foaf_name = new URIRef("http://xmlns.com/foaf/0.1/name");
159 var person_name = new PlainLiteral.with_lang("Person", "en");
160
161 var g = new Graph();
162 g.insert(new Statement(person, foaf_name, person_name));
163 assert_equal(
164 """<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">""" +
165 """<rdf:Description rdf:about="">""" +
166 """<foaf:name xmlns:foaf="http://xmlns.com/foaf/0.1/" xml:lang="en">Person</foaf:name>""" +
167 """</rdf:Description>""" +
168 """</rdf:RDF>""",
169 g.to_xml(person));
170 }
171
172 public void test_resource_object() {
173 var person = new URIRef("http://example.com/");
174 var buddy = new URIRef("http://example.com/buddy");
175 var foaf_knows = new URIRef("http://xmlns.com/foaf/0.1/knows");
176 var foaf_name = new URIRef("http://xmlns.com/foaf/0.1/name");
177 var buddy_name = new PlainLiteral("My Buddy");
178
179 var g = new Graph();
180 g.insert(new Statement(person, foaf_knows, buddy));
181 g.insert(new Statement(buddy, foaf_name, buddy_name));
182 assert_equal(
183 """<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">""" +
184 """<rdf:Description rdf:about="">""" +
185 """<foaf:knows xmlns:foaf="http://xmlns.com/foaf/0.1/">""" +
186 """<rdf:Description rdf:about="http://example.com/buddy">""" +
187 """<foaf:name>My Buddy</foaf:name>""" +
188 """</rdf:Description>""" +
189 """</foaf:knows>""" +
190 """</rdf:Description>""" +
191 """</rdf:RDF>""",
192 g.to_xml(person));
193 }
194
195 public void test_blank_object() {
196 var person = new URIRef("http://example.com/");
197 var buddy = new Blank();
198 var foaf_knows = new URIRef("http://xmlns.com/foaf/0.1/knows");
199 var foaf_name = new URIRef("http://xmlns.com/foaf/0.1/name");
200 var buddy_name = new PlainLiteral("My Buddy");
201
202 var g = new Graph();
203 g.insert(new Statement(person, foaf_knows, buddy));
204 g.insert(new Statement(buddy, foaf_name, buddy_name));
205 assert_equal(
206 """<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">""" +
207 """<rdf:Description rdf:about="">""" +
208 """<foaf:knows xmlns:foaf="http://xmlns.com/foaf/0.1/">""" +
209 """<rdf:Description>""" +
210 """<foaf:name>My Buddy</foaf:name>""" +
211 """</rdf:Description>""" +
212 """</foaf:knows>""" +
213 """</rdf:Description>""" +
214 """</rdf:RDF>""",
215 g.to_xml(person));
216 }
217
218 public void test_leaf_resource_object() {
219 var person = new URIRef("http://example.com/");
220 var foaf_knows = new URIRef("http://xmlns.com/foaf/0.1/knows");
221 var other_person = new URIRef("http://example.com/other");
222
223 var g = new Graph();
224 g.insert(new Statement(person, foaf_knows, other_person));
225 assert_equal(
226 """<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">""" +
227 """<rdf:Description rdf:about="">""" +
228 """<foaf:knows xmlns:foaf="http://xmlns.com/foaf/0.1/" """ +
229 """rdf:resource="http://example.com/other">""" +
230 """</foaf:knows>""" +
231 """</rdf:Description>""" +
232 """</rdf:RDF>""",
233 g.to_xml(person));
234 }
235
236 public void test_rdf_type() {
237 var person = new URIRef("http://example.com/");
238 var foaf_knows = new URIRef("http://xmlns.com/foaf/0.1/knows");
239 var other_person = new URIRef("http://example.com/other");
240 var foaf_person = new URIRef("http://xmlns.com/foaf/0.1/Person");
241
242 var g = new Graph();
243 g.insert(new Statement(person, foaf_knows, other_person));
244 g.insert(new Statement(other_person, new URIRef(RDF_NS + "type"), foaf_person));
245 assert_equal(
246 """<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">""" +
247 """<rdf:Description rdf:about="">""" +
248 """<foaf:knows xmlns:foaf="http://xmlns.com/foaf/0.1/">""" +
249 """<foaf:Person rdf:about="http://example.com/other">""" +
250 """</foaf:Person>""" +
251 """</foaf:knows>""" +
252 """</rdf:Description>""" +
253 """</rdf:RDF>""",
254 g.to_xml(person));
255 }
256
257 private void assert_equal(string expected, string actual) {
258 if (actual != expected) {
259 stderr.puts(@"\nActual: [$(actual)]\nExpected: [$(expected)]\n");
260 assert(actual == expected);
261 }
262 }
263
264 }
265
266 public void register_writer_tests() {
267 Test.add_func("/rdf/writer/test_literal_object", WriterTests.test_literal_object);
268 Test.add_func("/rdf/writer/test_resource_object", WriterTests.test_resource_object);
269 Test.add_func("/rdf/writer/test_blank_object", WriterTests.test_blank_object);
270 Test.add_func("/rdf/writer/test_leaf_resource_object", WriterTests.test_leaf_resource_object);
271 Test.add_func("/rdf/writer/test_rdf_type", WriterTests.test_rdf_type);
272 }
273
274 #endif
275
276 }