xmpedit

GTK+ editor for XMP metadata embedded in images
git clone https://code.djc.id.au/git/xmpedit/

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 }