changeset 94:a5f53d397526

view: pick types to show at top-level
author drewp@bigasterisk.com
date Wed, 12 Jan 2022 22:09:20 -0800
parents 955cde1550c3
children 47d3b5a5bd5e
files src/graph_view.ts src/tabulate.ts src/view_loader.ts
diffstat 3 files changed, 83 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/graph_view.ts	Wed Jan 12 20:26:57 2022 -0800
+++ b/src/graph_view.ts	Wed Jan 12 22:09:20 2022 -0800
@@ -211,11 +211,12 @@
 
   async makeTemplate(): Promise<TemplateResult> {
     await this.view.ready;
-    const { byType, typedSubjs, untypedSubjs } = groupByRdfType(this.graph);
+    const { byType, typesPresent, untypedSubjs } = groupByRdfType(this.graph);
     let viewTitle = html` (no view)`;
     if (this.view.url) {
       viewTitle = html` using view <a href="${this.view.url}">${this.view.label()}</a>`;
     }
+    const typesToShow = this.view.typesToShow(typesPresent);
     return html`
       <section>
         <h2>
@@ -226,7 +227,7 @@
             These statements are all in the
             <span data-bind="html: $root.createCurie(graphUri())">...</span> graph.-->
         </div>
-        ${typedSubjs.map((t: NamedNode) =>
+        ${typesToShow.map((t: NamedNode) =>
           this._multiSubjsTypeBlock(byType, t)
         )}
         <div class="spoGrid">
--- a/src/tabulate.ts	Wed Jan 12 20:26:57 2022 -0800
+++ b/src/tabulate.ts	Wed Jan 12 22:09:20 2022 -0800
@@ -44,7 +44,7 @@
 // When there are multiple types, an arbitrary one is used.
 export function groupByRdfType(
   graph: Store
-): { byType: TypeToSubjs; typedSubjs: NamedNode[]; untypedSubjs: NamedNode[] } {
+): { byType: TypeToSubjs; typesPresent: NamedNode[]; untypedSubjs: NamedNode[] } {
   let byType: TypeToSubjs = Immutable.Map();
   let untyped: UriSet = Immutable.Set(); // subjs
   const internSubjs = new Map<string, NamedNode>();
@@ -73,12 +73,12 @@
     null
   );
 
-  const typedSubjs = Array.from(byType.keys());
-  typedSubjs.sort();
+  const typesPresent = Array.from(byType.keys());
+  typesPresent.sort();
 
   const untypedSubjs = Array.from(untyped.values());
   untypedSubjs.sort();
-  return { byType: byType, typedSubjs: typedSubjs, untypedSubjs: untypedSubjs };
+  return { byType: byType, typesPresent: typesPresent, untypedSubjs: untypedSubjs };
 }
 
 export function predsForSubj(graph: Store, typeUri: NamedNode): NamedNode[] {
--- a/src/view_loader.ts	Wed Jan 12 20:26:57 2022 -0800
+++ b/src/view_loader.ts	Wed Jan 12 22:09:20 2022 -0800
@@ -1,5 +1,9 @@
 // Load requested view and provide access to it
-import { Store, Parser } from "n3";
+import { Store, Parser, Term, NamedNode, DataFactory, Util } from "n3";
+const Uri = DataFactory.namedNode;
+const RDFS = Util.prefix("http://www.w3.org/2000/01/rdf-schema#");
+const RDF = Util.prefix("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+const EX = Util.prefix("http://example.com/");
 
 async function fetchAndParse(url: string): Promise<Store> {
   const store = new Store();
@@ -21,10 +25,65 @@
   return store;
 }
 
+function _singleValue(g: Store, s: Term, p: Term): Term {
+  const quads = g.getQuads(s, p, null, null);
+  const objs = new Set(quads.map((q) => q.object));
+  if (objs.size == 0) {
+    throw new Error("no value for " + s.value + " " + p.value);
+  } else if (objs.size == 1) {
+    const obj = objs.values().next().value;
+    return obj as Term;
+  } else {
+    throw new Error("too many different values: " + JSON.stringify(quads));
+  }
+}
+
+function stringValue(g: Store, s: Term, p: Term): string {
+  const ret = _singleValue(g, s, p);
+  if (ret.termType != "Literal") {
+    throw new Error(`ret=${ret}`);
+  }
+  return ret.value as string;
+}
+
+function uriValue(g: Store, s: Term, p: Term): NamedNode {
+  const ret = _singleValue(g, s, p);
+  if (ret.termType != "NamedNode") {
+    throw new Error(`ret=${ret}`);
+  }
+
+  return ret;
+}
+
+function labelOrTail(g: Store, uri: NamedNode): string {
+  let ret: string;
+  try {
+    ret = stringValue(g, uri, RDFS("label"));
+  } catch (e) {
+    const words = uri.value.split("/");
+    ret = words[words.length - 1];
+  }
+  if (!ret) {
+    ret = uri.value;
+  }
+  return ret;
+}
+function objects(g: Store, subj: NamedNode, pred: NamedNode): Term[] {
+  return g.getObjects(subj, pred, null);
+}
+function firstElem<E>(seq: Iterable<E>): E {
+  for (let e of seq) {
+    return e;
+  }
+  throw new Error("no elems");
+}
+
 export class View {
   graph: Store;
   ready: Promise<null>;
+  viewRoot!: NamedNode;
   constructor(public url: string | "") {
+    (window as any).v = this; //debug
     this.graph = new Store();
     this.ready = new Promise((res, rej) => {
       if (url) {
@@ -36,8 +95,23 @@
         res(null);
       }
     });
+    this.ready.then(() => {
+      this.viewRoot = firstElem(
+        this.graph.getSubjects(RDF("type"), EX("View"), null)
+      ) as NamedNode;
+    });
   }
   label() {
-      return "nicelabel";
+    return labelOrTail(this.graph, Uri(this.url));
+  }
+  // filtered+ordered list of types to show at the top level
+  typesToShow(typesPresent: NamedNode[]): NamedNode[] {
+    const ret: NamedNode[] = [];
+    for (let table of this.graph.getObjects(this.viewRoot, EX("table"), null)) {
+      const tableType = uriValue(this.graph, table, EX("showsType"));
+      ret.push(tableType);
+    }
+    ret.sort();
+    return ret;
   }
 }