commit 0f970b261f52d6c4096fc7351e3af3e86ea90b50
parent 7afeb3d2b9359d5b0f7c3cf5001b3e3d3c24c420
Author: Dan Callaghan <djc@djc.id.au>
Date: Wed, 1 Sep 2010 21:58:01 +1000
first steps towards RDF writer (full of sucky error checking)
Diffstat:
5 files changed, 320 insertions(+), 6 deletions(-)
diff --git a/build b/build
@@ -19,18 +19,22 @@ def files_under(path):
result.extend(os.path.join(dirpath, filename) for filename in filenames)
return result
-def compile_vala(sources, target, pkgs=[], defs=[]):
- invoke(['valac', '-g', '--save-temps', '-d', 'target', '-o', target] +
+def compile_vala(sources, target, pkgs=[], defs=[], vapidirs=[]):
+ invoke(['valac', '--save-temps', '-d', 'target', '-o', target] +
+ ['--Xcc=-Ilib/genx'] + # XXX generalise
['--pkg=%s' % pkg for pkg in pkgs] +
['--define=%s' % define for define in defs] +
+ ['--vapidir=%s' % vapidir for vapidir in vapidirs] +
sources)
def main():
- pkgs = ['gtk+-2.0', 'gee-1.0', 'gexiv2', 'libxml-2.0', 'libsoup-2.4']
- main_sources = [f for f in files_under('src') if f.endswith('.vala')]
- compile_vala(sources=main_sources, target='xmpedit', pkgs=pkgs)
+ pkgs = ['gtk+-2.0', 'gee-1.0', 'gexiv2', 'libxml-2.0', 'libsoup-2.4', 'genx']
+ vapidirs = ['vapi']
+ main_sources = [f for f in files_under('src') if f.endswith('.vala') or f.endswith('.c')]
+ lib_sources = [f for f in files_under('lib') if f.endswith('.c')]
+ compile_vala(sources=main_sources + lib_sources, target='xmpedit', pkgs=pkgs, vapidirs=vapidirs, defs=['DEBUG'])
- compile_vala(sources=main_sources, target='xmpedit_test', pkgs=pkgs, defs=['TEST'])
+ compile_vala(sources=main_sources + lib_sources, target='xmpedit_test', pkgs=pkgs, vapidirs=vapidirs, defs=['DEBUG', 'TEST'])
invoke(['gtester', '--verbose', 'target/xmpedit_test'])
if __name__ == '__main__':
diff --git a/src/RDF.vala b/src/RDF.vala
@@ -131,6 +131,16 @@ public class Graph : Object {
Parser(this, base_uri).parse(xml);
}
+ /**
+ * N.B. not a general RDF writer,
+ * only includes statements reachable from start_node!
+ */
+ public string to_xml(URIRef start_node) {
+ var writer = new Writer(this);
+ writer.write(start_node);
+ return writer.get_xml();
+ }
+
public void insert(Statement statement) {
if (!has_matching_statement(statement.subject, statement.predicate, statement.object))
statements.add(statement);
@@ -207,6 +217,7 @@ private const string XML_NS = "http://www.w3.org/XML/1998/namespace";
public void register_tests() {
register_parser_tests();
+ register_writer_tests();
}
#endif
diff --git a/src/RDF_Writer.vala b/src/RDF_Writer.vala
@@ -0,0 +1,151 @@
+
+namespace RDF {
+
+private class Writer {
+
+ private Graph graph;
+ private Genx.Writer genx_writer = new Genx.Writer();
+ private StringBuilder output = new StringBuilder();
+
+ public Writer(Graph graph) {
+ this.graph = graph;
+ }
+
+ public string get_xml() {
+ return output.str;
+ }
+
+ /**
+ * N.B. not a general RDF writer,
+ * only includes statements reachable from start_node!
+ */
+ public void write(URIRef start_node) {
+ Genx.Status status;
+ status = genx_writer.start_doc(output);
+ assert(status == Genx.Status.SUCCESS);
+ unowned Genx.Namespace rdf_ns = genx_writer.declare_namespace(RDF_NS, "rdf", out status);
+ assert(status == Genx.Status.SUCCESS);
+ unowned Genx.Element rdf_el = genx_writer.declare_element(rdf_ns, "RDF", out status);
+ assert(status == Genx.Status.SUCCESS);
+
+ rdf_description_el = genx_writer.declare_element(rdf_ns, "Description", out status);
+ assert(status == Genx.Status.SUCCESS);
+ rdf_about_attr = genx_writer.declare_attribute(rdf_ns, "about", out status);
+ assert(status == Genx.Status.SUCCESS);
+ unowned Genx.Namespace xml_ns = genx_writer.declare_namespace(XML_NS, "xml", out status);
+ assert(status == Genx.Status.SUCCESS);
+ xml_lang_attr = genx_writer.declare_attribute(xml_ns, "lang", out status);
+ assert(status == Genx.Status.SUCCESS);
+
+ status = rdf_el.start();
+ assert(status == Genx.Status.SUCCESS);
+ write_resource(start_node);
+ status = genx_writer.end_element();
+ assert(status == Genx.Status.SUCCESS);
+ status = genx_writer.end_doc();
+ assert(status == Genx.Status.SUCCESS);
+ }
+
+ /* for efficiency */
+ private unowned Genx.Element rdf_description_el;
+ private unowned Genx.Attribute rdf_about_attr;
+ private unowned Genx.Attribute xml_lang_attr;
+
+ private void write_resource(URIRef node) {
+ Genx.Status status;
+ status = rdf_description_el.start();
+ assert(status == Genx.Status.SUCCESS);
+ status = rdf_about_attr.add(node.uri);
+ assert(status == Genx.Status.SUCCESS);
+ foreach (var statement in graph.find_matching_statements(node, null, null)) {
+ write_property(statement.predicate, statement.object);
+ }
+ status = genx_writer.end_element();
+ assert(status == Genx.Status.SUCCESS);
+ }
+
+ private void write_property(URIRef predicate, Node object) {
+ unowned Genx.Namespace ns;
+ string local_name;
+ split_uri(predicate.uri, out ns, out local_name);
+ Genx.Status status;
+ unowned Genx.Element property_el; // XXX reuse
+ property_el = genx_writer.declare_element(ns, local_name, out status);
+ assert(status == Genx.Status.SUCCESS);
+ status = property_el.start();
+ assert(status == Genx.Status.SUCCESS);
+ if (object is PlainLiteral) {
+ PlainLiteral literal = (PlainLiteral) object;
+ if (literal.lang != null) {
+ status = xml_lang_attr.add(literal.lang);
+ assert(status == Genx.Status.SUCCESS);
+ }
+ status = genx_writer.add_text(literal.lexical_value);
+ assert(status == Genx.Status.SUCCESS);
+ } else {
+ assert_not_reached();
+ }
+ status = genx_writer.end_element();
+ assert(status == Genx.Status.SUCCESS);
+ }
+
+ private void split_uri(string uri,
+ out unowned Genx.Namespace ns, out string local_name) {
+ var last_slash = uri.pointer_to_offset(uri.rchr(-1, '/')); // XXX crude
+ var ns_uri = uri.substring(0, last_slash + 1);
+ var ns_prefix = is_wellknown_ns(ns_uri);
+ Genx.Status status;
+ ns = genx_writer.declare_namespace(ns_uri, ns_prefix, out status);
+ assert(status == Genx.Status.SUCCESS);
+ local_name = uri.substring(last_slash + 1);
+ }
+
+ private string? is_wellknown_ns(string ns) {
+ for (int i = 0; i < WELL_KNOWN_NAMESPACES.length[0]; i ++)
+ if (WELL_KNOWN_NAMESPACES[i,1] == ns)
+ return WELL_KNOWN_NAMESPACES[i,0];
+ return null;
+ }
+
+ private static string[,] WELL_KNOWN_NAMESPACES = {
+ { "foaf", "http://xmlns.com/foaf/0.1/" }
+ };
+
+}
+
+#if TEST
+
+namespace WriterTests {
+
+public void test_literal_property() {
+ var person = new URIRef("http://example.com/");
+ var foaf_name = new URIRef("http://xmlns.com/foaf/0.1/name");
+ var person_name = new PlainLiteral.with_lang("Person", "en");
+
+ var g = new Graph();
+ g.insert(new Statement(person, foaf_name, person_name));
+ assert_equal(
+ """<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">""" +
+ """<rdf:Description rdf:about="http://example.com/">""" +
+ """<foaf:name xmlns:foaf="http://xmlns.com/foaf/0.1/" xml:lang="en">Person</foaf:name>""" +
+ """</rdf:Description>""" +
+ """</rdf:RDF>""",
+ g.to_xml(person));
+}
+
+private void assert_equal(string expected, string actual) {
+ if (actual != expected) {
+ stderr.puts(@"\nActual: [$(actual)]\nExpected: [$(expected)]\n");
+ assert(actual == expected);
+ }
+}
+
+}
+
+public void register_writer_tests() {
+ Test.add_func("/rdf/writer/test_literal_property", WriterTests.test_literal_property);
+}
+
+#endif
+
+}
diff --git a/src/genx-glib.c b/src/genx-glib.c
@@ -0,0 +1,29 @@
+
+#include <glib.h>
+#include <genx.h>
+
+static genxStatus _genx_send_gstring(void *userData, constUtf8 s) {
+ g_string_append((GString *)userData, (const gchar *)s);
+ return GENX_SUCCESS;
+}
+
+static genxStatus _genx_sendBounded_gstring(void *userData, constUtf8 start, constUtf8 end) {
+ g_string_append_len((GString *)userData, (const gchar *)start, (gssize) (end - start));
+ return GENX_SUCCESS;
+}
+
+static genxStatus _genx_flush_gstring(void *userData) {
+ return GENX_SUCCESS;
+}
+
+static genxSender _genx_sender_gstring = {
+ _genx_send_gstring,
+ _genx_sendBounded_gstring,
+ _genx_flush_gstring
+};
+
+genxStatus _genx_start_doc_gstring(genxWriter w, GString *gs) {
+ // XXX bad, we should hold a proper ref to gs here
+ genxSetUserData(w, (void *)gs);
+ genxStartDocSender(w, &_genx_sender_gstring);
+}
diff --git a/vapi/genx.vapi b/vapi/genx.vapi
@@ -0,0 +1,119 @@
+[CCode (cheader_filename = "genx.h")]
+namespace Genx {
+
+ [CCode (cname = "genxStatus", cprefix = "GENX_")]
+ public enum Status {
+ SUCCESS,
+ BAD_UTF8,
+ NON_XML_CHARACTER,
+ BAD_NAME,
+ ALLOC_FAILED,
+ BAD_NAMESPACE_NAME,
+ INTERNAL_ERROR,
+ DUPLICATE_PREFIX,
+ SEQUENCE_ERROR,
+ NO_START_TAG,
+ IO_ERROR,
+ MISSING_VALUE,
+ MALFORMED_COMMENT,
+ XML_PI_TARGET,
+ MALFORMED_PI,
+ DUPLICATE_ATTRIBUTE,
+ ATTRIBUTE_IN_DEFAULT_NAMESPACE,
+ DUPLICATE_NAMESPACE,
+ BAD_DEFAULT_DECLARATION
+ }
+
+ [Compact]
+ [CCode (cname = "struct genxWriter_rec", free_function = "genxDispose")]
+ public class Writer {
+
+ [CCode (has_target = 0)]
+ public delegate void* AllocCallback(void* user_data, int bytes);
+ [CCode (has_target = 0)]
+ public delegate void DeallocCallback(void* user_data, void* data);
+
+ [CCode (cname = "_genx_default_alloc")]
+ private static void* default_alloc(void* user_data, int bytes) {
+ return GLib.malloc(bytes);
+ }
+ [CCode (cname = "_genx_default_dealloc")]
+ private static void default_dealloc(void* user_data, void* data) {
+ GLib.free(data);
+ }
+
+ [CCode (cname = "genxNew")]
+ public Writer(
+ AllocCallback alloc = default_alloc,
+ DeallocCallback dealloc = default_dealloc,
+ void* user_data = null);
+
+ [CCode (cname = "genxDeclareNamespace")]
+ public unowned Namespace declare_namespace(string uri, string prefix, out Status status);
+
+ [CCode (cname = "genxDeclareElement")]
+ public unowned Element declare_element(Namespace ns, string type, out Status status);
+
+ [CCode (cname = "genxDeclareAttribute")]
+ public unowned Attribute declare_attribute(Namespace ns, string name, out Status status);
+
+ [CCode (cname = "_genx_start_doc_gstring")]
+ public Status start_doc(GLib.StringBuilder output);
+
+ [CCode (cname = "genxEndDocument")]
+ public Status end_doc();
+
+ [CCode (cname = "genxComment")]
+ public Status comment(string text);
+
+ [CCode (cname = "genxPI")]
+ public Status pi(string target, string text);
+
+ [CCode (cname = "genxStartElementLiteral")]
+ public Status start_element_literal(string xmlns, string type);
+
+ [CCode (cname = "genxAddAttributeLiteral")]
+ public Status add_attribute_literal(string xmlns, string name, string value);
+
+ [CCode (cname = "genxUnsetDefaultNamespace")]
+ public Status unset_default_namespace();
+
+ [CCode (cname = "genxEndElement")]
+ public Status end_element();
+
+ [CCode (cname = "genxAddText")]
+ public Status add_text(string text);
+
+ [CCode (cname = "genxAddCharacter")]
+ public Status add_character(int character);
+
+ }
+
+ [Compact]
+ [CCode (cname = "struct genxNamespace_rec", free_function = "NAMESPACE_HAS_NO_PUBLIC_FREE")]
+ public class Namespace {
+
+ [CCode (cname = "genxAddNamespace")]
+ public Status add(string? prefix = null);
+
+ }
+
+ [Compact]
+ [CCode (cname = "struct genxElement_rec", free_function = "ELEMENT_HAS_NO_PUBLIC_FREE")]
+ public class Element {
+
+ [CCode (cname = "genxStartElement")]
+ public Status start();
+
+ }
+
+ [Compact]
+ [CCode (cname = "struct genxAttribute_rec", free_function = "ATTRIBUTE_HAS_NO_PUBLIC_FREE")]
+ public class Attribute {
+
+ [CCode (cname = "genxAddAttribute")]
+ public Status add(string value);
+
+ }
+
+}