User GuideWelcome to the Turtle Editor Viewer. It's a place to write or load RDF, see it drawn as a diagram, and ask it questions with SPARQL. Everything runs in your browser, so nothing you paste in leaves your machine. It understands RDF 1.2 and SPARQL 1.2, with a few gaps listed in section 6, and it can check your data against SHACL shapes (section 5). This guide walks through what's on the screen and what each part does.
Where your data goes: nowhere. Parsing, drawing, querying, reasoning and validation all run inside the page. The only times the browser talks to another server are the ones you ask for: a URL you load, or a SERVICE clause in a query you run. Section 8 spells this out, including what the browser itself enforces.
The window is split into two panes, and you can drag the bar between them to change their widths:
The editor pane has a row of tabs along the top: the document you start with, plus any query results or extra files you open. More on that in a moment.
Three ways, take your pick:
.ttl), RDF/XML (.rdf, .xml), JSON-LD (.json, .jsonld) and Graphviz DOT (.dot) all work, and the tab takes the file's name.You can also put the URL straight into the page address so a link opens ready-loaded; see URL parameters at the end.
The editor works out the format from the content as you type, so you don't usually need to tell it. If it guesses wrong, the Language dropdown sets it by hand.
A document that uses relative IRIs (<hasChild> rather than <http://example.org/hasChild>, or a relative @id in JSON-LD) has them resolved against http://example.org/, in every syntax. An editor pane has no address of its own to offer, and the query and validation engines reject an entire graph that contains an IRI with no scheme, so resolving them keeps one such term from costing every query over the document.
The editor can hold several documents at once, each in its own tab:
CONSTRUCT or DESCRIBE query returns a graph (see Graph results). They're ordinary documents: edit them, save them, query them, draw them.Each tab remembers its own text, its own language and its own choice of subjects for the diagram, so flicking between tabs brings each diagram straight back. Close a tab with its ×; you can close any tab, including Source, except the last one standing. Closing a tab never touches what's in the others.
Two things to keep in mind:
The toolbar above the editor has:
Save File downloads the current tab's text (as turtle-file.ttl, so rename it once it lands).
The diagram is drawn with Graphviz from the subjects you pick.
Show labels, next to the filter box, swaps IRIs for names. If a subject has an rdfs:label or skos:prefLabel, the list shows that instead of ex:shop-0042. Where two subjects share a name the IRI is added after it so you can still tell them apart, and hovering over any entry shows its full IRI. It's on when the page opens; untick it to list IRIs.
Blank nodes show up too, when they're the top of a structure rather than nested inside another subject. Ones with a label in the file (_:g_00) are listed under that label; anonymous ones ([] a owl:AllDisjointClasses) are numbered anon-0, anon-1… in the order they appear. Those names stay put while you edit, so a blank node you've picked stays drawn.
The Graph toolbar lets you change how the diagram is laid out and drawn:
Dot gives tidy hierarchies, Neato and FDP are force-directed, Circo and Twopi are circular, Osage clusters.SVG is the one to use day to day; it's what you can pan and zoom. PNG gives a picture, and JSON, XDOT, Plain and PS are the raw Graphviz outputs.rdf:type instead of full IRIs.rdf:type arrows to cut the clutter.rdfs:label/skos:prefLabel names in the diagram, for the boxes and for the arrows respectively, the same way Show labels does for the subject list. Both are on by default. Turn them off to see the raw IRIs.RDF 1.2 lets a triple be talked about. In Turtle that looks like <<( :shop :founded "1921" )>> when you refer to the triple directly, or :shop :founded "1921" {| :claimedBy :source |} when you attach notes to it as you write it. Under the hood both use rdf:reifies and a triple term.
The diagram draws each triple term as its own node, in dark green, showing the triple it stands for. Blank nodes look just as they always have.
Tick Link Triple Terms and each green node also gets dashed dark-green arrows to the subject and the object it mentions, labelled subject and object, wherever those happen to be in the diagram. They don't affect the layout, so switching them on won't rearrange anything.
Click any node in the diagram and a panel opens listing everything in the data that refers to it — every subject of a triple whose object is the thing you clicked. It works for an IRI, a literal and a blank node alike, since all three are nodes you can click.
Each entry has a checkbox, ticked when that subject is already drawn:
So you can start from a single subject and walk the graph outwards by following what points at what, which is usually easier than hunting for the right name in the subject list — especially when the IRIs are opaque. The names in the panel are the same ones the subject list shows, labels and all, and hovering over an entry gives its full IRI.
The panel sits over the top-right of the diagram and stays until you close it with its ×, so you can tick several things in turn and watch the picture grow. Ticking here is the same act as selecting in the subject list: the two always agree, and whichever tab you are on keeps its own selection.
The SPARQL & SHACL panel at the bottom right runs SPARQL against the tab that's in front. (Its other job, validation, is section 5.)
PREFIX lines for every prefix declared in your data, so you needn't type them.query.sparql.The engine (Comunica) speaks SPARQL 1.2, so everything from 1.1 plus the newer additions:
SELECT: a table of answers.ASK: yes or no.CONSTRUCT and DESCRIBE: a graph, which can go straight into a new tab.{| |} in patterns, triple terms <<( )>>, and rdf:reifies, all match what's in your data. Literals with a text direction ("مرحبا"@ar--rtl) keep it.SELECT answers appear as a table. Language-tagged text shows its tag (hello@en, or مرحبا@ar--rtl when it has a direction) and a triple term is written out as <<( s p o )>>.
Export Results offers CSV or JSON for a table. The JSON follows the SPARQL 1.2 results format, so other 1.2-aware tools can read it, triple terms and all. For a graph result it saves Turtle as results.ttl.
CONSTRUCT and DESCRIBE)A graph result comes back as Turtle, written with the prefixes from your data, duplicates removed, and only the prefixes it actually uses declared at the top.
Where it goes depends on the Graph results to tab box among the SPARQL buttons:
Either way Export Results still saves it.
An empty table is the hardest result to read: it looks the same whether the data has no answer, a predicate is misspelled, or a FILTER threw everything away. Explain, next to Execute Query, answers it.
It shows the query plan: the steps the engine took, with the number of rows each one produced and how long it took. The diagram pane draws it as a tree with rows flowing upward from the bottom, and the results pane lists the same thing as a table.
What to look for is the step where the rows ran out. Reading from the bottom:
QuadPattern(?shop rdf:type bs:Bookshop) 33 rows 2 ms QuadPattern(?shop rdfs:label ?shop_name) 507 rows 4 ms LeftJoin(keys = ?shop) 33 rows QuadPattern(?shop bs:inSettlement ?s) 0 rows 0 ms LeftJoin(keys = ?shop) 0 rows <- nothing came out of here
33 bookshops were found, then a pattern matched nothing, and from there the query had no answer. The culprit is bs:inSettlement, a predicate that isn't in the data. The app marks that step in red, in both the diagram and the table, and says so in a line above the table, so you don't have to spot it yourself.
The same view shows the opposite problem: a step whose row count is far larger than its neighbours' is where a query got slow, usually a join with nothing to constrain it.
Three things to know:
HAVING can't see your AS names. In SELECT ?shop (COUNT(?x) AS ?n) … HAVING (?n > 1), ?n isn't bound yet when HAVING runs, so nothing comes back. Repeat the aggregate instead: HAVING (COUNT(?x) > 1). That's how SPARQL is specified, not a quirk of this engine.CONSTRUCT template holds triples only. Sub-selects, OPTIONAL, FILTER and other { } blocks belong in the WHERE part. Put one in the template and the parser will complain that it expected } and found {.SHACL is the other way to ask a question of your data: instead of a query that returns rows, a set of shapes that say what well-formed data looks like, and a report of everywhere the data falls short. The editor runs a full SHACL engine in the browser (the same one behind holos, compiled to WebAssembly): SHACL Core and SHACL-SPARQL, so sh:sparql constraints and SPARQL-based targets work too, and it reads RDF 1.2 data.
?dot=<data url>&shapes=<shapes url> loads the shapes into their own tab and picks it for you.The headline says Conforms or Does not conform, with the number of violations, warnings and infos and how many shapes were checked. Under it, one row per result:
Violation, Warning or Info. All three count against conformance, as the SHACL specification has it; the headline's counts tell you which kind you have.^ex:employs, (rdfs:subClassOf)+) rather than left as a blank node.sh:message, with {$this}, {$path} and {$value} filled in, or a description of the constraint if the shape has no message.ex:ShopShape › property 2.Report as tab opens the report itself as a new tab. It's an RDF graph (sh:ValidationReport, sh:ValidationResult), so you can draw it, query it (SELECT ?focus WHERE { ?r sh:focusNode ?focus }) or save it. Export report saves it straight to validation-report.ttl. Clear puts the panel back.
The SPARQL course ships shapes.ttl and shapes-advanced.ttl. Load bookshop-trail-1.1.ttl as data and shapes.ttl as shapes, validate, then change a shop's bs:staffCount to 0 and validate again: one new violation, "Staff count must be a positive integer", pointing at that shop.
--allow-warnings, count violations alone. If what matters is whether anything is a violation, read the counts rather than the headline.sh:lessThan between two xsd:gYear values (bs:born before bs:died, say) is reported as a violation by this engine, even when the years are in the right order. SPARQL's < isn't defined for gYear, and SHACL treats a comparison it can't make as a failure; some other validators compare the years anyway. So shapes.ttl reports eight of these on the untouched data, all from the one shape. Any other findings are real.SERVICE is not allowed inside shapes. A sh:sparql constraint that tries to reach a remote endpoint is rejected by the engine. Queries in the SPARQL editor can still use SERVICE.The parser (n3.js 2.2) and the query engine (Comunica 5.3) both speak the 1.2 versions of the standards, and the app has been checked against each feature below. Here's what you can rely on, and what to watch for.
VERSION "1.2" line; triple terms <<( s p o )>>, nested ones included; annotations {| … |}; reifiers, whether written ~ :r, << s p o >> or << s p o ~ :r >>; literals with a text direction such as "مرحبا"@ar--rtl; and rdf:JSON literals.@direction on a value is read and kept.WHERE patterns and in CONSTRUCT templates; the VERSION line; TRIPLE, SUBJECT, PREDICATE, OBJECT and isTRIPLE; LANGDIR, hasLANG, hasLANGDIR and STRLANGDIR.<<( s p o )>> and directions as @ar--rtl; the JSON export uses the SPARQL 1.2 results format ("type": "triple", "its:dir").rdf:parseType="Triple" is dropped without a word, and an its:dir direction is ignored (the language tag survives).@direction isn't written out."مرحبا"@ar--rtl looks like any other string. That's true of ordinary @en tags as well.rdfs:subPropertyOf) come back from the reasoner without the direction.<<( s p o )>>, and language tags and directions are left off.INSERT, DELETE, whether 1.1 or 1.2) isn't run. The panel only asks questions; change data in the editor.There's a built-in OWL 2 RL reasoner. Click Show Facts in the Graph toolbar and a new window lists the explicit triples (the ones in your data) and the implicit ones the reasoner worked out from them.
To Turtle and To JSON-LD in the Graph toolbar rewrite the active tab's data in that format, in place.
Add these to the page address to open it with data already loaded:
?dot=<url>: fetch a Turtle or DOT file from that address.?rdfa=<url>: fetch that address too. If it returns RDF it's loaded as normal; pulling RDFa out of an HTML page isn't implemented yet, so a web page just gets a note saying so.?shapes=<url>: fetch a SHACL shapes file into its own tab and select it for validation, leaving the data tab in front. Combine with ?dot= to hand someone data and shapes in one link.Remember to URL-encode the address you pass in.
This tool is built so that it can be used with data you'd rather not send anywhere. Here is exactly what that means, in plain terms, so you can decide for yourself.
The Turtle parser, the Graphviz renderer, the SPARQL engine (Comunica), the reasoner (Hylar) and the SHACL engine (a WebAssembly build of holos) all run inside your browser tab. They're served from the same place as the page itself, never from a content-delivery network, and none of them phones home. There's no analytics, no telemetry, no crash reporting, and no "look up this prefix online" convenience. The one such lookup a library used to carry (prefix.cc, inside a serialiser this app never calls) has been cut out of the build altogether.
?dot=, ?rdfa= and ?shapes= in the page address, fetch the address you give. The server at that address sees the request, as any web fetch does. Nothing from your editor goes with it.SERVICE clause in a query you run. Federation is part of SPARQL, and the course uses it. When your query names an endpoint, the part of the query inside the SERVICE block, along with the bindings needed to answer it, goes to that endpoint. That's the one way anything from the tab reaches another server, and it happens only because the query you wrote says so. If you've pasted something sensitive into the editor, don't then run a SERVICE query that joins it against a public endpoint.That's the whole list. In particular:
@context is a URL would normally make the parser go and get it; here it's refused with a message asking you to paste the context into the document. That request would have been made by the document, not by you.SERVICE inside sh:sparql constraints outright.The page carries a Content Security Policy, so the rules above aren't just good intentions. Scripts run only from the page's own origin: no inline scripts, no javascript: links, no eval. The page may connect only to its own origin and to HTTP(S) addresses, which is what Load URL and SERVICE need. If a future change, or a bug, tried to load a script from elsewhere or run code smuggled in through a data file, the browser would refuse and say so in the console.
Data can't run code, either. Everything that comes from a file, IRIs, labels, literal text, is drawn, listed and printed as text. A literal containing <script> or <img onerror=…> is just an ugly string.
http: or https: address, because that's what SERVICE needs. To restrict it to approved endpoints, send a Content-Security-Policy header from your web server with a tighter connect-src (say, connect-src 'self' https://sparql.example.internal); the browser applies both, and the stricter one wins. Drop http: if all your endpoints are HTTPS.npm audit) and pruned to what the app actually imports. The one advisory that reaches the browser is against the DOMPurify copy inside the Monaco editor's own bundle: five advisories, one of them critical, each in a sanitiser option Monaco does not use, and the only markdown it sanitises is the app's own static text. Monaco's copy is out of our hands, so the editor is kept current as a matter of course. The rest are development tools that never reach the built app. SECURITY.md has the detail.?dot=https://… fetches whatever address the link names. That's the point of it, and it's also why you should treat such a link like any other link: it will load that file into your editor.eval internally for rules with arithmetic in them. The OWL 2 RL rules it ships with have none, so that path is never taken here, and the policy above would refuse it if it were. Adding custom rules on top of Hylar would need a different reasoner.SECURITY.md file in the repository says how to repeat them.