commit c67614722aeee3096b43eb0398900eeab2bc719e
parent 7e0718158dafde0e220d0a07e11a6b62941e72f6
Author: Dan Callaghan <djc@djc.id.au>
Date: Mon, 9 Aug 2010 22:43:39 +1000
support some more RDF/XML features (enough to parse some rudimentary XMP packets, yay)
Diffstat:
M | src/RDF.vala | | | 184 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- |
1 file changed, 165 insertions(+), 19 deletions(-)
diff --git a/src/RDF.vala b/src/RDF.vala
@@ -37,9 +37,10 @@ public class URIRef : SubjectNode {
public class Blank : SubjectNode {
- public string id { get; construct; }
+ /** This is for informational purposes only! Not a unique id for equality! */
+ public string? id { get; construct; }
- public Blank(string id) {
+ public Blank(string? id) {
Object(id: id);
}
@@ -48,7 +49,9 @@ public class Blank : SubjectNode {
}
public override string to_string() {
- return @"_:$id";
+ if (id != null)
+ return @"_:$id";
+ return "Blank@%p".printf(&this);
}
}
@@ -117,7 +120,7 @@ public class Graph : Object {
}
private Gee.List<Statement> statements =
- new Gee.LinkedList<Statement>((EqualFunc) Statement.equal);
+ new Gee.ArrayList<Statement>((EqualFunc) Statement.equal);
private string? base_uri;
public Graph() {
@@ -132,9 +135,10 @@ public class Graph : Object {
Xml.Node* root = doc->get_root_element();
if (root == null)
throw new ParseError.EMPTY_XML("root == null");
- if (root->name != "RDF" || root->ns->href != RDF_NS)
- throw new ParseError.DOCUMENT_ELEMENT_NOT_FOUND("root was not <rdf:RDF>");
- for (Xml.Node* child = root->children; child != null; child = child->next) {
+ var document_element = find_rdf_document_element(root);
+ if (document_element == null)
+ throw new ParseError.DOCUMENT_ELEMENT_NOT_FOUND("no <rdf:RDF> element");
+ for (Xml.Node* child = document_element->children; child != null; child = child->next) {
if (child->type != Xml.ElementType.ELEMENT_NODE)
continue;
parse_node_element(child);
@@ -144,14 +148,37 @@ public class Graph : Object {
}
}
+ // XXX use explicit stack instead of recursion
+ private Xml.Node* find_rdf_document_element(Xml.Node* element) {
+ if (element->name == "RDF" || element->ns->href == RDF_NS)
+ return element;
+ for (Xml.Node* child = element->children; child != null; child = child->next) {
+ if (child->type != Xml.ElementType.ELEMENT_NODE)
+ continue;
+ var found = find_rdf_document_element(child);
+ if (found != null)
+ return found;
+ }
+ return null;
+ }
+
// XXX intern URIs and lang tags
- private void parse_node_element(Xml.Node* element) throws ParseError {
+ private SubjectNode parse_node_element(Xml.Node* element) throws ParseError {
// determine resource URI
+ SubjectNode subject;
var subject_uri = element->get_ns_prop("about", RDF_NS);
- if (subject_uri == null)
- throw new ParseError.ILLEGAL_RDFXML("missing rdf:about attribute");
- var subject = new URIRef(resolve_uri(subject_uri, base_uri));
+ if (subject_uri != null)
+ subject = new URIRef(resolve_uri(subject_uri, base_uri));
+ else
+ subject = new Blank(null);
+
+ // is it a typed element?
+ if (!(element->name == "Description" && element->ns->href == RDF_NS)) {
+ statements.add(new Statement(subject,
+ new URIRef(RDF_NS + "type"),
+ new URIRef(element->ns->href + element->name)));
+ }
// handle attributes
// skip rdf:about, xml:lang, rdf:parseType
@@ -170,10 +197,12 @@ public class Graph : Object {
continue;
parse_property_element(subject, child);
}
+
+ return subject;
}
- private void parse_property_attribute(URIRef subject, Xml.Attr* attr) {
- var property = new URIRef(attr->ns->href + attr->name);
+ private void parse_property_attribute(SubjectNode subject, Xml.Attr* attr) {
+ var predicate = new URIRef(attr->ns->href + attr->name);
Node object;
if (attr->name == "type" && attr->ns->href == RDF_NS) {
object = new URIRef(attr->children->content);
@@ -184,17 +213,17 @@ public class Graph : Object {
else
object = new PlainLiteral(attr->children->content);
}
- statements.add(new Statement(subject, property, object));
+ statements.add(new Statement(subject, predicate, object));
}
- private void parse_property_element(URIRef subject, Xml.Node* element) {
- var property = new URIRef(element->ns->href + element->name);
+ private void parse_property_element(SubjectNode subject, Xml.Node* element) throws ParseError {
+ var predicate = new URIRef(element->ns->href + element->name);
// is the object a URI ref? (rdf:resource)
var object_uri = element->get_ns_prop("resource", RDF_NS);
if (object_uri != null) {
var object = new URIRef(object_uri);
- statements.add(new Statement(subject, property, object));
+ statements.add(new Statement(subject, predicate, object));
return;
}
@@ -206,18 +235,37 @@ public class Graph : Object {
object = new PlainLiteral.with_lang(element->get_content(), lang);
else
object = new PlainLiteral(element->get_content());
- statements.add(new Statement(subject, property, object));
+ statements.add(new Statement(subject, predicate, object));
return;
}
// need to recurse
- // XXX
+ for (Xml.Node* child = element->children; child != null; child = child->next) {
+ if (child->type != Xml.ElementType.ELEMENT_NODE)
+ continue;
+ var object = parse_node_element(child);
+ statements.add(new Statement(subject, predicate, object));
+ break; // ignore any other child elements, not legal anyway
+ }
}
public Gee.Collection<Statement> get_statements() {
return statements;
}
+ public Gee.Collection<Statement> find_matching_statements(
+ SubjectNode? subject, URIRef? predicate, Node? object) {
+ // XXX naive
+ var result = new Gee.ArrayList<Statement>((EqualFunc) Statement.equal);
+ foreach (var s in statements) {
+ if ((subject == null || s.subject.equals(subject)) &&
+ (predicate == null || s.predicate.equals(predicate)) &&
+ (object == null || s.object.equals(object)))
+ result.add(s);
+ }
+ return result;
+ }
+
}
private const string RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
@@ -321,12 +369,110 @@ public void test_unicode() {
new PlainLiteral.with_lang("\xd0\xb4\xd0\xb5\xd0\xbd\xd1\x8c", "ru"))));
}
+public void test_find_rdf_root() {
+ var g = new Graph.from_xml("""
+ <ex:other xmlns:ex="http://some.other.crap/">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <rdf:Description rdf:about=""
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xml:lang="en">
+ <dc:description>Some stuff.</dc:description>
+ </rdf:Description>
+ </rdf:RDF>
+ </ex:other>
+ """, "http://example.com/");
+ assert(g.get_statements().size == 1);
+ assert(g.get_statements().contains(new Statement(
+ new URIRef("http://example.com/"),
+ new URIRef("http://purl.org/dc/elements/1.1/description"),
+ new PlainLiteral.with_lang("Some stuff.", "en"))));
+}
+
+public void test_nested_property_elements() {
+ var g = new Graph.from_xml("""
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <rdf:Description rdf:about=""
+ xmlns:foaf="http://xmlns.com/foaf/0.1/">
+ <foaf:knows>
+ <rdf:Description rdf:about="http://example.com/buddy">
+ <foaf:name>My Buddy</foaf:name>
+ </rdf:Description>
+ </foaf:knows>
+ </rdf:Description>
+ </rdf:RDF>
+ """, "http://example.com/");
+ assert(g.get_statements().size == 2);
+ assert(g.get_statements().contains(new Statement(
+ new URIRef("http://example.com/"),
+ new URIRef("http://xmlns.com/foaf/0.1/knows"),
+ new URIRef("http://example.com/buddy"))));
+ assert(g.get_statements().contains(new Statement(
+ new URIRef("http://example.com/buddy"),
+ new URIRef("http://xmlns.com/foaf/0.1/name"),
+ new PlainLiteral("My Buddy"))));
+}
+
+public void test_blank() {
+ var g = new Graph.from_xml("""
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <rdf:Description rdf:about=""
+ xmlns:foaf="http://xmlns.com/foaf/0.1/">
+ <foaf:knows>
+ <rdf:Description>
+ <foaf:name>My Buddy</foaf:name>
+ </rdf:Description>
+ </foaf:knows>
+ </rdf:Description>
+ </rdf:RDF>
+ """, "http://example.com/");
+ assert(g.get_statements().size == 2);
+ var statements = g.find_matching_statements(
+ new URIRef("http://example.com/"),
+ new URIRef("http://xmlns.com/foaf/0.1/knows"),
+ null);
+ assert(statements.size == 1);
+ Blank blank;
+ {
+ Gee.Iterator<Statement> it = statements.iterator();
+ it.next();
+ blank = (Blank) it.get().object;
+ }
+ assert(g.find_matching_statements(
+ blank,
+ new URIRef("http://xmlns.com/foaf/0.1/name"),
+ new PlainLiteral("My Buddy")).size == 1);
+}
+
+public void test_typed_node_element() {
+ var g = new Graph.from_xml("""
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <foaf:Person rdf:about=""
+ xmlns:foaf="http://xmlns.com/foaf/0.1/">
+ <foaf:name>Person</foaf:name>
+ </foaf:Person>
+ </rdf:RDF>
+ """, "http://example.com/");
+ assert(g.get_statements().size == 2);
+ assert(g.find_matching_statements(
+ new URIRef("http://example.com/"),
+ new URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
+ new URIRef("http://xmlns.com/foaf/0.1/Person")).size == 1);
+ assert(g.find_matching_statements(
+ new URIRef("http://example.com/"),
+ new URIRef("http://xmlns.com/foaf/0.1/name"),
+ new PlainLiteral("Person")).size == 1);
+}
+
public void register_tests() {
Test.add_func("/xmpedit/rdf/test_property_attributes", test_property_attributes);
Test.add_func("/xmpedit/rdf/test_property_attributes_rdf_type", test_property_attributes_rdf_type);
Test.add_func("/xmpedit/rdf/test_property_elements", test_property_elements);
Test.add_func("/xmpedit/rdf/test_property_elements_inherit_lang", test_property_elements_inherit_lang);
Test.add_func("/xmpedit/rdf/test_unicode", test_unicode);
+ Test.add_func("/xmpedit/rdf/test_find_rdf_root", test_find_rdf_root);
+ Test.add_func("/xmpedit/rdf/test_nested_property_elements", test_nested_property_elements);
+ Test.add_func("/xmpedit/rdf/test_blank", test_blank);
+ Test.add_func("/xmpedit/rdf/test_typed_node_element", test_typed_node_element);
}
#endif