Mapping Turtle files to Javascript/Typescript objects

Hi Community,

This is probably a silly question, but how can one seamlessly convert RDF Quads into Javascript objects?

Intent: I am trying to display and navigate RDF data in Typescript. I have Turtle files containing RDF entities, and TS classes that model these entities. I use the TS classes to populate Vue.js templates and render these entities dynamically.

To some extent, I’m trying to replicate JSON-LD parsing into an object with TTL.

Given the following file (skipping prefixes),

<#example-en> a lime:Lexicon ;
  rdfs:label "Example wordnet (English)"@en ;
  dc:language "en" ;
  schema:email "john@mccr.ae" ;
  cc:license <https://creativecommons.org/publicdomain/zero/1.0/> ;
  owl:versionInfo "1.0" ;
  schema:citation "CILI: the Collaborative Interlingual Index. Francis Bond, Piek Vossen, John P. McCrae and Christiane Fellbaum, Proceedings of the Global WordNet Conference 2016, (2016)." ;
  schema:url "http://globalwordnet.github.io/schemas/" ;
  dc:publisher "Global Wordnet Association" ;
  lime:entry <#w1>, <#w2>, <#w3> .

is it possible to parse the TTL file, then access field values in the following manner:

const data = load(ttl); // magic function parsing TTL string

const publisher = data['publisher']; // "Global Wordnet Association"
const name = data['label']['en']; // "Example wordnet (English)"

JSON-LD parsing

Stack: I am currently using Vue.js, Typescript and N3.js.
Using N3.Parser, I can read TTL files into arrays of Quads, which I am then unable to convert to JS objects in a “straightforward” way.

I am constrained by the fact this needs to be done on the client side (browser).

Any piece of advice or suggested reading will be appreciated :slightly_smiling_face:
Many thanks!

NB: I am relatively new to Typescript and RDF, so I’m not really sure that this is the right way to go.

Hey @HBailly

I think I have a solution that works for you, but it involves using ShEx. Maybe it is helpful to you?

You could specify a shape that has the properties you want to read from the turtle file:

PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX myname: <http://mynamespace.example/#>
...

myname:Wordnet {
  a [lime:Lexicon]
    // rdfs:comment  "Defines the node as a Lexicon (from lime)" ;
  rdfs:label xsd:string;
    // rdfs:comment  "The name of the Lexicon" ;
  dc:publisher xsd:string;
    // rdfs:comment  "The name of the publisher of the Lexicon" ;
}

Once you have defined how the “Shape” of your turtle file looks like, so what properties you want to read and validate, you can use this Shape Expression to generate the typescript types using shex-codegen. The readme has the instructions to set it up, but if you want even more integration you could try shex-methods in combination with shex-codegen which with the above shape expression could be used like this:

import { wordnet } from "../generated/shex"

const { data } = await wordnet.findOne({ 
  where: { 
    id: "https://example.com#example-en" 
  }, 
  doc: "https://example.com" 
})
console.debug(data)
/* { id: "https://example.com#example-en", "type":  "https://www.w3.org/ns/lemon/lime#Lexicon", "label": "Example wordnet (English)", "publisher":  "Global Wordnet Association" } */

I have to mention that I only developed these libraries for educational purposes and after personal preferences and while they have some tests they are not really that stable right now. Any feedback/bugs you find and report are therefore highly appreciated :slight_smile: Also if you want to use these libraries with a version of webpack 5 or higher you’ll need to polyfill the missing core nodejs libraries that are being used by shex.js which is under the hood of the libraries.

Hi Ludwig,

Thank you for this!

I looked into your libraries, and managed to generate several instances of Shapes without hassle.
It looks like what I was going after, yes :smiley:

What I am about to ask now is probably contradictory with the whole idea of RDF, but… is it possible to read a wordnet from a local TTL file rather than from a remote resource?

When I try to use a local URI, it throws FailFetch error with Only absolute URLs are supported.

1 Like

When I try to use a local URI, it throws FailFetch error with Only absolute URLs are supported .

If you mean a file:/// url, those can be supported with Solid Rest.

@HBailly
like @jeffz mentioned you could use Solid Rest’s fetch function to read local files, since shex-methods uses rdflib:

const client = new SolidRestFile();

wordnet.fetcher._fetch = client.fetch.bind(client);

Alternatively you could use solid-node-client (which uses solid-rest), like i did in the test suite of shex-methods:

const client = new SolidNodeClient();
...
wordnet.fetcher._fetch = client.fetch.bind(client);
1 Like

If you are only going to be accessing file:/// URLs, use Solid-Rest, but if you are going to be accessing both file:/// and http*:// URLs, use Solid-Node-Client which will seamlessly switch fetches depending on context.

1 Like

Thank you @jeffz, I managed to read the file using the solid-node-client as suggested.

I had some trouble with the turtle file (which used PREFIX instead of @prefix . directive), but it now reads and parse the file without apparent error.

1 Like

@ludwigschubi

Now that I can read the file, it fails at being recognized as a valid instance of the shape, and I am not sure why?
Would you mind checking if this is caused by the Shex format?

console.log
    {
      doc: 'file:///C://Users//HBail//Git////SOLID//src//main//webapp//content//ttl//application.ttl',
      data: undefined,
      errors: [
        'validating https://projectron.example/#id as http://example.com/ApplicationShape:',
        '    Missing property: http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
        '  OR',
        '  Missing property: http://www.w3.org/ns/solid/interop#applicationName',
        '  OR',
        '  Missing property: http://www.w3.org/ns/solid/interop#applicationDescription',
        '  OR',
        '  Missing property: http://www.w3.org/ns/solid/interop#applicationAuthor',
        '  OR',
        '  Missing property: http://www.w3.org/ns/solid/interop#applicationThumbnail',
        '  OR',
        '  Missing property: http://www.w3.org/ns/solid/interop#hasAccessNeedGroup'
      ]
    }

This is the Jest code calling the file.

  it('should load application shape', async () => {
    const client = new SolidNodeClient();

    application.fetcher._fetch = client.fetch.bind(client);

    const data = await application.findOne({
      where: {id: 'https://projectron.example/#id'},
      doc: "file:///C://Users//HBail//Git////SOLID//src//main//webapp//content//ttl//application.ttl"
    });
    console.log(data);

  });

This is the file being read.

@prefix interop:       <http://www.w3.org/ns/solid/interop#>.
@prefix projectron:    <https://projectron.example/#>.
@prefix needs:         <https://projectron.example/needs#>.
@prefix acme:          <https://acme.example/#>.

<projectron:id>
    a                              interop:Application ;
    interop:applicationName        "Projectron" ;
    interop:applicationDescription "Manage projects with ease" ;
    interop:applicationAuthor      acme:id ;
    interop:applicationThumbnail   <acme:thumb.svg> ;
    interop:hasAccessNeedGroup     needs:need-group-pm .

And this is the Shex shape against which it is controlled

PREFIX rdf:           <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX interop:       <http://www.w3.org/ns/solid/interop#>
PREFIX xsd:           <http://www.w3.org/2001/XMLSchema#>

<ApplicationShape> {
    a [interop:Application],
	interop:applicationName         xsd:string,
    interop:applicationDescription  xsd:string,
    interop:applicationAuthor       IRI,
    interop:applicationThumbnail    IRI,
    interop:hasAccessNeedGroup      IRI
    }

I have not been able to proceed step-by-step in the validateShapes function to find out more.

EDIT: When I tested with the Employee shape and instance from shex.io, it ran without issue, so it is definitely my files that are the issue… :disappointed_relieved:

Is it maybe because of the trailing dot at the end of the turtle file ? :thinking:

Don’t have a computer right now but I would take another look later

Sorry, that was a copy-paste issue in my post. This dot isn’t there in the original file.
Fixed here and there.

@prefix interop:       <http://www.w3.org/ns/solid/interop#>.
@prefix projectron:    <https://projectron.example/#>.
@prefix needs:         <https://projectron.example/needs#>.
@prefix acme:          <https://acme.example/#>.

<projectron:id>
    a                              interop:Application ;
    interop:applicationName        "Projectron" ;
    interop:applicationDescription "Manage projects with ease" ;
    interop:applicationAuthor      acme:id ;
    interop:applicationThumbnail   <acme:thumb.svg> ;
    interop:hasAccessNeedGroup     needs:need-group-pm .

If you want to learn about more tools, you can take a look at the libraries listed in solidproject.org.

In particular, you may be interested to look at LDFlex, I think it’s close to what you want to do (I’m not sure because I haven’t used it). Another option would be to use soukai, which is the one I use for my apps which are also written in Vue with Typescript (disclaimer: I made it :D).

Thank you for this, I will have a look at both, see if they fit my case :slightly_smiling_face:

@ludwigschubi this is caused by the ID of the entity vs the filter condition.

This works

Status Filter Value
:white_check_mark: where: {id: 'https://projectron.example/#id'} <https://projectron.example/#id>
:white_check_mark: where: {id: 'projectron:id'} <projectron:id>

but the following doesn’t

Status Filter Value
:x: where: {id: 'https://projectron.example/#id'} <projectron:id>
:x: where: {id: 'projectron:id'} <https://projectron.example/#id>
1 Like

Hm and what happens when you use ‘projectron:id’ in your turtle file without the <> brackets? And is there a difference between the two :thinking: Also not an expert on rdf/rdflib therefore I don’t really understand what is the expected behavior here :man_shrugging:

EDIT: I guess both should work interchangeably but I don’t really know maybe there is a difference between those 2 forms that needs to accounted for

EDIT: of course, it cannot work with interop:id => this is not the right prefix :man_facepalming:
Using where: {id: 'projectron:id'} with projectron:id is correctly resolved…

Removing the <> brackets in the turtle file breaks it:

where: {id: 'projectron:id'} does not work with interop:id any more, and http://www.w3.org/ns/solid/interop#id causes a FailFetch.

I would too have expected them to work interchangeably :thinking: If I’m not mistaken, the prefix interop is a shorthand for http://www.w3.org/ns/solid/interop# and can be named at one’s discretion.

  • If two parties use different prefixes for the same resources, then this should be resolved regardless of the prefixes?
  • if two parties use the same prefix for different URL, then it would be a problem if this is not resolved?
1 Like

@NoelDeMartin, I tried Soukai but I do not see how I can read a TTL file?

I am trying to populate a model that I created.

import { SolidModel } from 'soukai-solid';

export class SocialAgent extends SolidModel {

  static rdfContexts = {
    'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
    'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
    'interop': 'http://www.w3.org/ns/solid/interop#',
    'alice': 'https://alice.example/',
    'alice-jarvis': 'https://alice.jarvis.example/',
    'alice-auth': 'https://auth.alice.example/',
    'alice-inbox': 'https://alice.example/inbox/',
  };
  static rdfsClasses = ['interop:SocialAgent'];
}

I see that there is a LocalStorageEngine but not sure how this one works?

    SoukaiSolid.loadSolidModels();
    Soukai.loadModels({SocialAgent});

    let engine = new LogEngine(new LocalStorageEngine());
    Soukai.useEngine(engine);

    const agents = await SocialAgent.from('C:/Users/HBail/OneDrive/Documents/Git/SOLID/src/src/main/webapp/content/ttl/alice.ttl').all();
    console.log(agents);

The file I am trying to read is the following turtle

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX interop: <http://www.w3.org/ns/solid/interop#>
PREFIX alice: <https://alice.example/>
PREFIX alice-jarvis: <https://alice.jarvis.example/>
PREFIX alice-auth: <https://auth.alice.example/>
PREFIX alice-inbox: <https://alice.example/inbox/>

alice:\#id
  a interop:SocialAgent ;
  ######## Registry Sets ########
  interop:hasRegistrySet alice:registries ;
  ######## Authorization Agent ########
  interop:hasAuthorizationAgent alice-jarvis: ;
  ######## Inboxes ########
  interop:hasInbox alice-inbox:general ;
  interop:hasAccessInbox alice-inbox:access .

foo:bar adds whatever you defined as a prefix for foo: to the front of bar. <foo:bar> will assume that foo: is a protocol like https and try to load it and fail because there is no such protocol. "foo:bar" is a string and not a URL or CURIE.

1 Like

^^In other words, if you use a prefix, do NOT put angle brackets or quotes around it.

  • If two parties use different prefixes for the same resources, then this should be resolved regardless of the prefixes?

Yes, the prefix is defined in an @prefix statement and the URL in that statement is what is used regardless of what prefix is attached to it.

  • if two parties use the same prefix for different URL, then it would be a problem if this is not resolved?

No, it does not matter at all what the prefix is. What matters is what URL the prefix is assigned to in the @prefix statement of turtle or the PREFIX statement of SPARQL>

A prefix is pretty much a variable. If you define foo: as meaning <http://example.com/>, then wherever you put foo: it is as if you wrote the full URL. If two apps define foo: as two different things, it will mean something different in each app. If one app defines foo: as <http://example.com/> and another defines bar as the same URL, the prefixes will have the same meaning even though they are different.

1 Like

It’s a per-document/graph thing. If foo: means X in one document and Y in another, it makes no difference. All of the foo: instances in the first document will be replaced with X and all of the foo: instances in the second document will be replaced with Y.