CIM Notebooks
CIM Notebooks are CIMNotebook's own notebook documents for VS Code: markdown files whose
```sparql and ```shacl code blocks open as notebook cells. Because the
on-disk format is plain markdown, notebooks diff and merge cleanly in git, render on any
git forge, and need no special tooling to read.
Every code cell is validated by CIMLangServer exactly like a standalone .rq or .shacl
file — diagnostics, hover, completion, and go-to-definition all work inside cells, resolved
against your configured CGMES schema (see
SPARQL Notebooks for how a cell picks its schema with the
# [endpoint=...] directive).
CIM Notebooks are a VS Code feature. The IntelliJ plugin validates
.rq, .sparql, .ttl, and .shacl files but has no notebook support.
The notebook feature is inspired by the
SPARQL Notebook
extension by Zazuko (MIT-licensed) — CIM Notebooks are an independent implementation that
reads and writes Zazuko's .sparqlbook format for interoperability.
File formats
| Format | Opens as notebook | Notes |
|---|---|---|
*.cimnb.md | by default | The native format. A regular markdown file — the suffix just tells VS Code to open it as a notebook. |
any *.md / *.markdown | via Open With… → CIM Notebook (Markdown) | Turn any markdown document (a README, a runbook) into a notebook without renaming it. |
*.sparqlbook | via Open With… → CIM Notebook (SPARQL Book) | Zazuko SPARQL Notebook interop (JSON). |
To use Open With…: right-click the file in the Explorer → Open With… → pick the CIM Notebook editor. VS Code remembers the choice per file type if you set it as default.
The markdown format
Only top-level, unindented three-backtick fences labelled sparql or shacl become code
cells. Everything else — prose, headings, tables, code blocks in other languages (```turtle,
```json, …) — stays markdown:
# Switch survey
Count switches per substation:
```sparql
SELECT ?substation (COUNT(?switch) AS ?n)
WHERE { ?switch cim:Equipment.EquipmentContainer ?substation }
GROUP BY ?substation
```
Shapes for the same data:
```shacl
ex:SwitchShape a sh:NodeShape ;
sh:targetClass cim:Switch .
```
Saving a notebook normalizes the file deterministically: cells are separated by exactly one blank line, trailing blank lines inside cells are dropped, and the file ends with a single newline. Line endings (LF/CRLF) are preserved. Cell outputs are never written to disk — notebook files stay pure source.
Converting between formats
The command CIMNotebook: Convert Notebook (Markdown ⇔ SPARQL Book) writes the active
notebook in the other format as a sibling file (report.sparqlbook → report.cimnb.md and
back) and opens it. The source file is never modified. Zazuko per-cell metadata survives a
.sparqlbook round trip but is dropped when converting to markdown.
Running cells
SPARQL and SHACL cells have a Run button (the CIM Notebook kernel). Execution happens inside CIMLangServer — the same Java process that validates your queries — using Apache Jena's SPARQL 1.1 and SHACL engines, so there is nothing extra to install.
A cell names its target with the same # [endpoint=...] directive used for validation —
one directive drives both validate against and run against. The target can be a SPARQL
endpoint or local data files:
# [endpoint=http://localhost:3030/cgmes/query]
SELECT ?class (COUNT(?s) AS ?n)
WHERE { ?s a ?class }
GROUP BY ?class ORDER BY DESC(?n)
# [endpoint=./model.xml]
SELECT ?name WHERE { ?s cim:IdentifiedObject.name ?name }
What you get per query kind:
| Query | Output |
|---|---|
SELECT | Result table (first 50 rows rendered; the full result travels alongside as application/sparql-results+json) |
ASK | ✅ true / ❌ false |
CONSTRUCT / DESCRIBE | The result graph as Turtle |
INSERT / DELETE / other updates | A confirmation with the update endpoint used |
| SHACL cell | A conforms / does-not-conform verdict with severity counts, plus the full validation report as Turtle |
Each output ends with a small stats line — row count, duration, and the resolved endpoint. Results are capped server-side (10 000 rows/triples by default) and the output says so when the cap was hit. The raw payload of a result is one click away: the output's ⋯ → Change Presentation menu switches between the rendered view and the plain-text payload (the SPARQL Results JSON, or the Turtle of a graph/report).
Updates run against the endpoint's update service: for Fuseki-style URLs the client
derives it from the query URL (…/query or …/sparql → …/update); any other URL is
assumed to accept updates directly.
Cancel and timeout. The notebook Stop button cancels the running request (the server stops waiting and aborts the query where the endpoint supports it). Requests time out after 30 seconds by default.
Querying local files — including CIMXML models
When a directive is not an http(s):// URL it is taken as a file path, relative to the
notebook's own directory. The file is parsed in-process and queried directly — no triple
store, no import step:
- CIMXML models (
*.xml): parsed by OpenCGMES's IEC 61970-552 parser. The model body and itsmd:FullModelheader are both queryable. - RDF files: anything Jena reads by extension —
.ttl,.rdf,.owl,.nt,.nq,.trig, ….
Several directives in one cell query the union of the files: named graphs stay
addressable with GRAPH, and a bare ?s ?p ?o sees everything.
# [endpoint=./model.xml]
# [endpoint=./boundary.ttl]
SELECT (COUNT(*) AS ?triples) WHERE { ?s ?p ?o }
A directive can also be a glob pattern — * matches within a directory, ** crosses
directories, and {a,b} picks alternatives, just like the SPARQL Notebook extension:
# [endpoint=./rdf/*.ttl]
SELECT (COUNT(*) AS ?triples) WHERE { ?s ?p ?o }
# [endpoint=./rdf/{a,b}.ttl]
SELECT (COUNT(*) AS ?triples) WHERE { ?s ?p ?o }
A pattern that matches no files fails the run with an error (it won't silently query nothing); a file matched by several directives is read once.
Parsed files are cached in the language server and re-parsed automatically when the file changes on disk, so edit model → re-run cell just works. Local files are read-only: a SPARQL Update against a file target is rejected — updates need an HTTP endpoint.
Multiple file directives are the only repetition that means something. A cell runs against exactly one endpoint, so mixing kinds (a file and a URL, a file and a connection name) or repeating a URL or a connection name is reported as an error rather than silently resolved to one of them — remove the directives you didn't mean.
Note the dual meaning of the directive: for validation, the .ttl/.rdf/.owl files
are loaded as the schema (several files or a pattern's matches form one union schema),
while instance-data files (.xml models, .nt/.nq/.trig dumps) keep the workspace
schema so diagnostics stay meaningful — and either way the files are what the cell runs
against. So # [endpoint=./model.xml] + # [endpoint=./schema.ttl] queries both files
while validating the query against schema.ttl.
Running SHACL cells
A SHACL cell's text is the shapes graph; running the cell validates the target data
against those shapes. The same # [endpoint=...] directives pick the data:
-
Local files — the shapes are checked in-process (Apache Jena SHACL) against the union of the referenced files, CIMXML models included:
# [endpoint=./model.xml]ex:SwitchNameShape a sh:NodeShape ;sh:targetClass cim:Switch ;sh:property [ sh:path cim:IdentifiedObject.name ; sh:minCount 1 ] . -
HTTP endpoints — the shapes are POSTed as Turtle to the endpoint's SHACL service (Fuseki's
shacloperation and compatibles), which validates its own data. For Fuseki-style URLs the service is derived from the query URL (…/query→…/shacl); because Fuseki requires a graph selector,?graph=default(the default graph) is added automatically — put an explicit?graph=…in the directive to validate a named graph.
The output is a ✅ conforms / ❌ does-not-conform banner with counts per severity
(violations, warnings, infos), followed by the full sh:ValidationReport as Turtle. A
run that finds violations is still a successful run — non-conformance is the result,
not an error.
Named connections
Endpoints you use often can be declared once in opencgmes.jsonc (the same file that
configures validation) under a cimnotebook section, and referenced by name:
{
"cimnotebook": {
"connections": [
{
"name": "local-fuseki",
"url": "http://localhost:3030/cgmes/query",
"default": true,
},
{
"name": "prod",
"url": "https://sparql.example.org/query",
"authType": "basic",
},
],
"queryTimeoutSeconds": 30, // workspace default for cell executions
"maxRows": 10000, // workspace default result cap
},
}
# [endpoint=local-fuseki]
SELECT * WHERE { ?s ?p ?o } LIMIT 10
A connection name has no slashes and no dots — that's how it is told apart from a file
path. updateUrl/shaclUrl are optional and derived from url when omitted.
Connections (and the rest of the config) can also be edited in the CIMNotebook
sidebar — see Configuration sidebar.
Every cell shows its resolved target in the cell status bar (local-fuseki, a URL,
model.xml (+1), or a no endpoint warning); clicking it — or running CIMNotebook:
Set Cell Endpoint… — picks a connection, a URL (with a small recent-URLs history above
the input box), or one or more data files via fuzzy, search-as-you-type matching over the
workspace (with Browse… for files outside it and Enter path manually… for a file
that doesn't exist yet). Picking several files writes one # [endpoint=...] directive
per file — the same union target described above. One constraint inherited from the
directive syntax: a path containing spaces cannot be referenced (the directive value ends
at the first whitespace character).
Where a cell without a directive runs
A cell with no # [endpoint=...] line of its own is not stranded — the target is resolved
in three steps, and the first one that answers wins:
- The cell's own directive.
- The notebook default. Set Cell Endpoint… offers to apply a single connection, URL,
or file as the Notebook default instead of writing it into the cell. It is remembered
in VS Code's workspace state — deliberately not in the notebook file and not in
opencgmes.jsonc— so it is a private, per-workspace convenience: a notebook shared with a colleague carries only its directives. The picker shows the current default in its title and offers Clear the notebook default while one is set. - The default connection. The
opencgmes.jsoncconnection marked"default": true.
Validation follows the same three steps, so a cell is always validated against the endpoint
it runs against: schema-based diagnostics, completion, hover, and go-to-definition all use
the schema of the resolved target (a .ttl/.rdf/.owl file or the endpoint's schema
graphs). A target that carries no schema — a CIMXML model, an .nt dump — keeps the
workspace schema from opencgmes.jsonc instead, since instance data would only produce
false diagnostics.
Credentials
Connections with "authType": "basic" prompt once for username and password on first
run and offer to keep them in VS Code secret storage (the OS keychain). Passwords are
never written to opencgmes.jsonc, VS Code settings, or the notebook — they travel
only inside the execute request to the local language server, which turns them into the
HTTP Authorization header. Manage them with CIMNotebook: Set Connection
Credentials… and Clear Connection Credentials….
The CIMNotebook (trace) output channel (LSP trace, off by default) logs full request
payloads — including credentials of an execute request. Leave tracing off when working
with authenticated endpoints, or clear the channel afterwards.
Current limitations:
- Only HTTP basic authentication is supported.