Search code examples
javaxmlnamespacesrdf4j

org.eclipse.rdf4j.rio.RDFWriter, how to set base namespace when writing document


I am using the rdf4j library to export xml files. I use org.eclipse.rdf4j.rio.RDFWriter to save it in a file and I need to define the base namespace. So I expect to have something like :

<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xml:base="http://exmaple.org/">
<rdf:Description rdf:about=foo>
<rdf:type rdf:resource= FOO>
...

I understood that I can set a namespace using handleNamespace(). But I cant figure out how to use it to set the base namespace : I tried :

ModelBuilder builder = new ModelBuilder();
ValueFactory vf = SimpleValueFactory.getInstance();
builder.add(vf.createIRI("base:foo"), RDFS.ISDEFINEDBY, vf.createIRI("base:FOO");
Model model = builder.build();
RDFWriter writer = Rio.createWriter(RDFFormat.RDFXML, someOutputStream);
try {
  writer.startRDF();
  writer.handleNamespace("base", "http://exmaple.org/");
      for (Statement st: model) {
  writer.handleStatement(st);
  }
  writer.endRDF();
...

but this does not give me what I expect :

<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:base="http://exmaple.org/">
<rdf:Description rdf:about=base:foo>
<rdf:type rdf:resource= base:FOO>

Any ideas?


Solution

  • A namespace is not the same thing as a base URI, and there is no such thing as a "base namespace". Namespaces are an abbreviation mechanism for URIs where you define a prefix that replaces the first part of a full URI with a short string, so that in your RDF data you can represent the URI with a "prefixed name" (depending on the specific RDF syntax what that looks like, exactly).

    Base URIs, on the other hand, are a generic parser/writer mechanism that allows resolving relative URIs in the data. Some syntax formats (like XML) allow defining the base URI in the document itself, others don't, but they serve a different purpose than namespaces.

    As an example, this XML snippet:

    <?xml version="1.0" encoding="UTF-8"?>
    <rdf:RDF
    xmlns:base="http://example.org/">
      <rdf:Description rdf:about="foo">
    ....
    

    is legal RDF, despite the fact that the RDF/XML syntax spec requires that the value of the rdf:about attribute (the subject) is a full URI. The reason it's legal is that a base URI is defined against which the string foo can be resolved as a relative URI. The resolved full URI will be http://example.org/foo. Note that this has nothing to do with namespaces.

    What is happening in your code is that you are not using base URIs at all. In the first part you are defining an RDF model in memory, using the ModelBuilder. You are adding the string base:foo as a subject URI. The ModelBuilder (actually, the ValueFactory, but it comes tot the same thing) interprets this as a full URI, because there is no namespace defined in the ModelBuilder that has base as its prefix.

    Writing that model to XML file does not change that, regardless of what you define as base URI or namespaces while writing: you are exporting an RDF model that has base:foo as a full URI, so that's what you'll get in your output.

    To get what you want, you need to make sure that your Model object has the full URIs, and that, instead of trying add a namespace to the writer, you set its base URI when creating the writer.

            var model = new ModelBuilder().add("http://example.org/foo", RDFS.ISDEFINEDBY, "http://example.org/FOO").build();
            var writer = Rio.createWriter(RDFFormat.RDFXML, System.out, "http://example.org/");
    
            writer.startRDF();
            for (Statement st : model) {
                writer.handleStatement(st);
            }
            writer.endRDF();
    

    ...or more succinctly:

            var model = new ModelBuilder().add("http://example.org/foo", RDFS.ISDEFINEDBY, "http://example.org/FOO").build();
    
            Rio.write(model, System.out, "http://example.org/", RDFFormat.RDFXML);
    

    If you don't like having to write the full URI every time when creating your model, that's where namespaces can come in handy, for example:

            var model = new ModelBuilder().setNamespace("ex", "http://example.org/")
                    .add("ex:foo", RDFS.ISDEFINEDBY, "ex:FOO").build();