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 }