changeset 103:f12feced00ce

WIP rewriting Layout
author drewp@bigasterisk.com
date Sat, 12 Mar 2022 00:42:00 -0800
parents ab7dca42afbd
children 1aea03d306af
files src/Layout.test.ts src/Layout.ts src/fetchAndParse.ts src/namespaces.ts src/tabulate.test.ts src/tabulate.ts
diffstat 6 files changed, 383 insertions(+), 232 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Layout.test.ts	Sat Mar 12 00:42:00 2022 -0800
@@ -0,0 +1,169 @@
+import { Layout, LayoutResult } from "./Layout";
+import { ViewConfig } from "./ViewConfig";
+import {
+  DataFactory,
+  Store,
+  Prefixes,
+  Parser,
+  Quad,
+  NamedNode,
+  Term,
+} from "n3";
+import { EX, rdf } from "./namespaces";
+import Immutable from "immutable";
+import { n3Graph } from "./fetchAndParse";
+const { namedNode } = DataFactory;
+
+const twoStatements = async (): Promise<Store> => {
+  return n3Graph(`
+  @prefix : <http://example.com/> .
+  :g1 {
+    :a :b :c .
+    :d :e :f .
+  }
+  `);
+};
+
+const typedStatements = async (): Promise<Store> => {
+  return n3Graph(`
+  @prefix : <http://example.com/> .
+  :g1 {
+    :a a :T1 ; :color :red .
+    :b a :T1 ; :color :blue .
+    :c a :T1 .
+    :d a :T2 ; :size :big .
+    :e a :T1,:T2; :size :small
+  }
+  `);
+};
+function G1(s: Term, p: Term, o: Term): Quad {
+  return new Quad(s, p, o, EX("g1"));
+}
+
+describe("Layout", () => {
+  it("accepts a ViewConfig", async () => {
+    const vc = new ViewConfig();
+    await vc.readFromGraph(`
+      @prefix ex: <http://example.com/> .
+      @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+      <> a ex:View; rdfs:label "repos" .`);
+    const layout = new Layout(vc);
+    const lr = layout.plan(await twoStatements());
+  });
+  it("defaults to putting all triples in the ungrouped list", async () => {
+    const layout = new Layout();
+    const lr = layout.plan(await twoStatements());
+    expect(lr).toEqual({
+      sections: [
+        {
+          statements: [
+            G1(EX("a"), EX("b"), EX("c")),
+            G1(EX("d"), EX("e"), EX("f")),
+          ],
+        },
+      ],
+    });
+  });
+  describe("makes a table as requested by ViewConfig", () => {
+    let lr: LayoutResult;
+
+    beforeAll(async () => {
+      const vc = new ViewConfig();
+      await vc.readFromGraph(`
+        @prefix : <http://example.com/> .
+        @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+  
+        <> a :View; :table [ :primaryType :T1 ] .`);
+      const layout = new Layout(vc);
+      const lr = layout.plan(await typedStatements());
+      expect(lr.sections).toHaveLength(2);
+    });
+    it("puts the right type in the table", async () => {
+      expect(lr.sections[0]).toEqual({
+        columnHeaders: [{ rdfType: EX("T1"), pred: EX("color") }],
+        rows: [
+          [EX("a"), EX("red")],
+          [EX("b"), EX("blue")],
+          [EX("c"), null],
+          [EX("e"), null],
+        ],
+      });
+    });
+    it("leaves the rest ungrouped", async () => {
+      expect(lr.sections[1]).toEqual({
+        statements: [
+          G1(EX("d"), rdf.type, EX("T1")),
+          G1(EX("d"), EX("size"), EX("big")),
+        ],
+      });
+    });
+  });
+  it("makes a table out of ungrouped triples with the same type", async () => {});
+});
+
+// describe("equality", () => {
+//   test("investigation of https://github.com/rdfjs/N3.js/issues/265", () => {
+//     const x = namedNode("x");
+//     const x2 = namedNode("x");
+//     // (NamedNode.prototype as any).hashCode = () => 0;
+//     // expect((x as any).hashCode()).toEqual((x2 as any).hashCode())
+//     expect(x === x2).toBeFalsy();
+//     expect(x == x2).toBeFalsy();
+//     expect(x.equals(x2)).toBeTruthy();
+//     let imap = Immutable.Map();
+//     imap = imap.set(x, 11);
+//     imap = imap.set(x, 22);
+//     imap = imap.set(x2, 33);
+//     expect(imap.has(x)).toBeTruthy();
+//     expect(imap.has(x2)).toBeTruthy();
+//     expect(imap.size).toEqual(1);
+//   });
+// });
+
+// describe("groupByRdfType", () => {
+//   test("finds multiple graphs", () => {});
+//   test("works", async () => {
+//     const store = new Store();
+
+//     const parser = new Parser();
+//     await new Promise((res, rej) => {
+//       parser.parse(
+//         `PREFIX : <urn:>
+//         :rs1 a :Foo; :pred1 "obj1" .
+//         :rs2 a :Foo; :pred1 "obj2" .
+//         :rs3 a :Bar .
+//         :rs4 :pred1 "obj4" .
+// `,
+//         (error, quad: Quad, prefixes: Prefixes) => {
+//           if (quad) {
+//             store.addQuad(quad);
+//           } else {
+//             res(undefined);
+//           }
+//         }
+//       );
+//     });
+//     const grouped = groupByRdfType(store);
+//     expect(Array.from(grouped.byType.keys())).toHaveLength(2);
+//     expect(grouped.byType.get(namedNode("urn:Foo"))).toEqual(
+//       Immutable.Set([namedNode("urn:rs1"), namedNode("urn:rs2")])
+//     );
+//     expect(grouped.byType.get(namedNode("urn:Bar"))).toEqual(
+//       Immutable.Set([namedNode("urn:rs3")])
+//     );
+//     expect(grouped.untypedSubjs).toEqual([namedNode("urn:rs4")]);
+//   });
+
+//   describe("MultiSubjsTypeBlockLayout", () => {
+//     test("gathers subjs", () => {
+
+//     });
+//     test("gathers preds", () => {
+
+//     });
+//     test("cells reports filled cells", () => {
+
+//     });
+//   });
+// });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Layout.ts	Sat Mar 12 00:42:00 2022 -0800
@@ -0,0 +1,201 @@
+// Organize graph data into tables (column orders, etc) for the view layer.
+
+import Immutable from "immutable"; // mostly using this for the builtin equals() testing, since NamedNode(x)!=NamedNode(x)
+import {
+  DataFactory,
+  NamedNode,
+  Quad,
+  Quad_Object,
+  Store,
+  Term,
+  Util,
+} from "n3";
+import { ViewConfig } from "./ViewConfig";
+
+const { namedNode } = DataFactory;
+
+// // import ns from 'n3/src/IRIs';
+// // const { rdf } = ns;
+
+type UriSet = Immutable.Set<NamedNode>;
+export type TypeToSubjs = Immutable.Map<NamedNode, UriSet>;
+
+// https://github.com/rdfjs/N3.js/issues/265
+if ((NamedNode.prototype as any).hashCode === undefined) {
+  (NamedNode.prototype as any).hashCode = () => 0;
+}
+
+interface ColumnHeader {
+  rdfType?: NamedNode; // could be more than one column that introduces an rdf:type for a join
+  pred: NamedNode;
+}
+interface AlignedTable {
+  columnHeaders: ColumnHeader[];
+  rows: (Term | null)[][]; // each row is 1 wider than columnHeaders since the 1st element is the subject for that row
+}
+interface FreeStatements {
+  statements: Quad[];
+}
+export interface LayoutResult {
+  sections: (AlignedTable | FreeStatements)[];
+}
+
+export class Layout {
+  constructor(public viewConfig?: ViewConfig) {}
+  plan(graph: Store): LayoutResult {
+    const ungrouped: Quad[] = [];
+
+    graph.forEach(
+      (q: Quad) => {
+        ungrouped.push(q);
+      },
+      null,
+      null,
+      null,
+      null
+    );
+    return { sections: [{ statements: ungrouped }] };
+  }
+}
+
+// function getType(graph: Store, subj: NamedNode): NamedNode | null {
+//   let subjType: NamedNode | null = null;
+
+//   graph.forObjects(
+//     (o: Quad_Object) => {
+//       subjType = o as NamedNode;
+//     },
+//     subj,
+//     rdf.type,
+//     null
+//   );
+//   return subjType;
+// }
+
+// // When there are multiple types, an arbitrary one is used.
+// export function groupByRdfType(
+//   graph: Store
+// ): {
+//   byType: TypeToSubjs;
+//   typesPresent: NamedNode[];
+//   untypedSubjs: NamedNode[];
+// } {
+//   let byType: TypeToSubjs = Immutable.Map();
+//   let untyped: UriSet = Immutable.Set(); // subjs
+//   const internSubjs = new Map<string, NamedNode>();
+//   graph.forEach(
+//     (q) => {
+//       if (!Util.isNamedNode(q.subject)) {
+//         throw new Error("unsupported " + q.subject.value);
+//       }
+//       const subj = q.subject as NamedNode;
+
+//       const subjType = getType(graph, subj);
+
+//       if (subjType !== null) {
+//         // (subj, rdf:type, subjType) in graph
+//         const oldKeys = Array.from(byType.keys());
+//         const oldVal = byType.get(subjType, Immutable.Set<NamedNode>());
+//         const newVal = oldVal.add(subj);
+//         byType = byType.set(subjType, newVal);
+//       } else {
+//         untyped = untyped.add(subj);
+//       }
+//     },
+//     null,
+//     null,
+//     null,
+//     null
+//   );
+
+//   const typesPresent = Array.from(byType.keys());
+//   typesPresent.sort();
+
+//   const untypedSubjs = Array.from(untyped.values());
+//   untypedSubjs.sort();
+//   return {
+//     byType: byType,
+//     typesPresent: typesPresent,
+//     untypedSubjs: untypedSubjs,
+//   };
+// }
+
+// export function predsForSubj(graph: Store, typeUri: NamedNode): NamedNode[] {
+//   const predsSet: Set<NamedNode> = new Set();
+//   graph.forEach(
+//     (q: Quad) => {
+//       predsSet.add(q.predicate as NamedNode);
+//     },
+//     typeUri,
+//     null,
+//     null,
+//     null
+//   );
+//   const preds = Array.from(predsSet.values());
+//   preds.sort();
+//   return preds;
+// }
+
+// interface ISP {
+//   subj: NamedNode;
+//   pred: NamedNode;
+// }
+// const SP = Immutable.Record<ISP>({
+//   subj: new NamedNode(""),
+//   pred: new NamedNode(""),
+// });
+
+// // One table of rows with a common rdf:type.
+// export class MultiSubjsTypeBlockLayout {
+//   subjs: NamedNode[];
+//   preds: NamedNode[];
+//   graphCells: Immutable.Map<ISP, Immutable.Set<Term>>;
+//   constructor(graph: Store, byType: TypeToSubjs, table: TableDesc) {
+//     const subjSet = byType.get(table.primary);
+//     this.subjs = subjSet ? Array.from(subjSet) : [];
+//     this.subjs.sort();
+
+//     let preds = Immutable.Set<NamedNode>();
+
+//     this.graphCells = Immutable.Map<ISP, Immutable.Set<Term>>().withMutations(
+//       (mutGraphCells) => {
+//         this.subjs.forEach((subj: NamedNode) => {
+//           graph.forEach(
+//             (q: Quad) => {
+//               if (!Util.isNamedNode(q.predicate)) {
+//                 throw new Error();
+//               }
+
+//               const pred = q.predicate as NamedNode;
+//               if (pred.equals(rdf.type)) {
+//                 // the whole block is labeled with the type
+//                 return;
+//               }
+//               preds = preds.add(pred);
+//               const cellKey = this.makeCellKey(subj, pred);
+//               mutGraphCells.set(
+//                 cellKey,
+//                 mutGraphCells.get(cellKey, Immutable.Set<Term>()).add(q.object)
+//               );
+//             },
+//             subj,
+//             null,
+//             null,
+//             null
+//           );
+//         });
+//       }
+//     );
+//     this.preds = Array.from(preds);
+//     this.preds.splice(this.preds.indexOf(rdf.type), 1);
+//     // also pull out label, which should be used on 1st column
+//     this.preds.sort();
+//   }
+
+//   makeCellKey(subj: NamedNode, pred: NamedNode): ISP {
+//     return SP({
+//       subj: subj,
+//       pred: pred,
+//     });
+//   }
+// }
--- a/src/fetchAndParse.ts	Fri Mar 11 23:19:35 2022 -0800
+++ b/src/fetchAndParse.ts	Sat Mar 12 00:42:00 2022 -0800
@@ -14,14 +14,17 @@
     store = new Store();
   }
 
-  const parser = new Parser({ format: "N3" });
-  return new Promise((res, rej) => {
+  const parser = new Parser({ format: "TriG" });
+  await new Promise((res, rej) => {
     parser.parse(n3, (error, quad: Quad, prefixes: Prefixes) => {
+      if (error) rej(error);
       if (quad) {
         store!.addQuad(quad);
       } else {
-        res(store!);
+        res(undefined);
       }
     });
   });
+
+  return store;
 }
--- a/src/namespaces.ts	Fri Mar 11 23:19:35 2022 -0800
+++ b/src/namespaces.ts	Sat Mar 12 00:42:00 2022 -0800
@@ -1,5 +1,11 @@
-import { Util } from "n3";
+import { DataFactory, Util } from "n3";
+const { namedNode } = DataFactory;
 
 export const RDFS = Util.prefix("http://www.w3.org/2000/01/rdf-schema#");
 export const RDF = Util.prefix("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
 export const EX = Util.prefix("http://example.com/");
+
+export const rdf = {
+    type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
+  };
+  
\ No newline at end of file
--- a/src/tabulate.test.ts	Fri Mar 11 23:19:35 2022 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-import { groupByRdfType } from "./tabulate";
-import {
-  Literal,
-  DataFactory,
-  Store,
-  Prefixes,
-  Parser,
-  Quad,
-  NamedNode,
-} from "n3";
-import Immutable from "immutable";
-const { namedNode } = DataFactory;
-
-describe("equality", () => {
-  test("investigation of https://github.com/rdfjs/N3.js/issues/265", () => {
-    const x = namedNode("x");
-    const x2 = namedNode("x");
-    // (NamedNode.prototype as any).hashCode = () => 0;
-    // expect((x as any).hashCode()).toEqual((x2 as any).hashCode())
-    expect(x === x2).toBeFalsy();
-    expect(x == x2).toBeFalsy();
-    expect(x.equals(x2)).toBeTruthy();
-    let imap = Immutable.Map();
-    imap = imap.set(x, 11);
-    imap = imap.set(x, 22);
-    imap = imap.set(x2, 33);
-    expect(imap.has(x)).toBeTruthy();
-    expect(imap.has(x2)).toBeTruthy();
-    expect(imap.size).toEqual(1);
-  });
-});
-
-describe("groupByRdfType", () => {
-  test("finds multiple graphs", () => {});
-  test("works", async () => {
-    const store = new Store();
-
-    const parser = new Parser();
-    await new Promise((res, rej) => {
-      parser.parse(
-        `PREFIX : <urn:>
-        :rs1 a :Foo; :pred1 "obj1" .
-        :rs2 a :Foo; :pred1 "obj2" .
-        :rs3 a :Bar .
-        :rs4 :pred1 "obj4" .
-`,
-        (error, quad: Quad, prefixes: Prefixes) => {
-          if (quad) {
-            store.addQuad(quad);
-          } else {
-            res(undefined);
-          }
-        }
-      );
-    });
-    const grouped = groupByRdfType(store);
-    expect(Array.from(grouped.byType.keys())).toHaveLength(2);
-    expect(grouped.byType.get(namedNode("urn:Foo"))).toEqual(
-      Immutable.Set([namedNode("urn:rs1"), namedNode("urn:rs2")])
-    );
-    expect(grouped.byType.get(namedNode("urn:Bar"))).toEqual(
-      Immutable.Set([namedNode("urn:rs3")])
-    );
-    expect(grouped.untypedSubjs).toEqual([namedNode("urn:rs4")]);
-  });
-});
--- a/src/tabulate.ts	Fri Mar 11 23:19:35 2022 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-// Organize graph data into tables (column orders, etc) for the view layer.
-
-import Immutable from "immutable"; // mostly using this for the builtin equals() testing, since NamedNode(x)!=NamedNode(x)
-import {
-  DataFactory,
-  NamedNode,
-  Quad,
-  Quad_Object,
-  Store,
-  Term,
-  Util,
-} from "n3";
-import { TableDesc } from "./view_loader";
-
-const { namedNode } = DataFactory;
-
-// // import ns from 'n3/src/IRIs';
-// // const { rdf } = ns;
-export const rdf = {
-  type: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
-};
-
-type UriSet = Immutable.Set<NamedNode>;
-export type TypeToSubjs = Immutable.Map<NamedNode, UriSet>;
-
-// https://github.com/rdfjs/N3.js/issues/265
-if ((NamedNode.prototype as any).hashCode === undefined) {
-  (NamedNode.prototype as any).hashCode = () => 0;
-}
-
-function getType(graph: Store, subj: NamedNode): NamedNode | null {
-  let subjType: NamedNode | null = null;
-
-  graph.forObjects(
-    (o: Quad_Object) => {
-      subjType = o as NamedNode;
-    },
-    subj,
-    rdf.type,
-    null
-  );
-  return subjType;
-}
-
-// When there are multiple types, an arbitrary one is used.
-export function groupByRdfType(
-  graph: Store
-): { byType: TypeToSubjs; typesPresent: NamedNode[]; untypedSubjs: NamedNode[] } {
-  let byType: TypeToSubjs = Immutable.Map();
-  let untyped: UriSet = Immutable.Set(); // subjs
-  const internSubjs = new Map<string, NamedNode>();
-  graph.forEach(
-    (q) => {
-      if (!Util.isNamedNode(q.subject)) {
-        throw new Error("unsupported " + q.subject.value);
-      }
-      const subj = q.subject as NamedNode;
-
-      const subjType = getType(graph, subj);
-
-      if (subjType !== null) {
-        // (subj, rdf:type, subjType) in graph
-        const oldKeys = Array.from(byType.keys());
-        const oldVal = byType.get(subjType, Immutable.Set<NamedNode>());
-        const newVal = oldVal.add(subj);
-        byType = byType.set(subjType, newVal);
-      } else {
-        untyped = untyped.add(subj);
-      }
-    },
-    null,
-    null,
-    null,
-    null
-  );
-
-  const typesPresent = Array.from(byType.keys());
-  typesPresent.sort();
-
-  const untypedSubjs = Array.from(untyped.values());
-  untypedSubjs.sort();
-  return { byType: byType, typesPresent: typesPresent, untypedSubjs: untypedSubjs };
-}
-
-export function predsForSubj(graph: Store, typeUri: NamedNode): NamedNode[] {
-  const predsSet: Set<NamedNode> = new Set();
-  graph.forEach(
-    (q: Quad) => {
-      predsSet.add(q.predicate as NamedNode);
-    },
-    typeUri,
-    null,
-    null,
-    null
-  );
-  const preds = Array.from(predsSet.values());
-  preds.sort();
-  return preds;
-}
-
-interface ISP {
-  subj: NamedNode;
-  pred: NamedNode;
-}
-const SP = Immutable.Record<ISP>({
-  subj: new NamedNode(""),
-  pred: new NamedNode(""),
-});
-
-export class MultiSubjsTypeBlockLayout {
-  subjs: NamedNode[];
-  preds: NamedNode[];
-  graphCells: Immutable.Map<ISP, Immutable.Set<Term>>;
-  constructor(graph: Store, byType: TypeToSubjs, table: TableDesc) {
-    const subjSet = byType.get(table.primary);
-    this.subjs = subjSet ? Array.from(subjSet) : [];
-    this.subjs.sort();
-
-    let preds = Immutable.Set<NamedNode>();
-
-    this.graphCells = Immutable.Map<ISP, Immutable.Set<Term>>().withMutations(
-      (mutGraphCells) => {
-        this.subjs.forEach((subj: NamedNode) => {
-          graph.forEach(
-            (q: Quad) => {
-              if (!Util.isNamedNode(q.predicate)) {
-                throw new Error();
-              }
-
-              const pred = q.predicate as NamedNode;
-              if (pred.equals(rdf.type)) {
-                // the whole block is labeled with the type
-                return;
-              }
-              preds = preds.add(pred);
-              const cellKey = this.makeCellKey(subj, pred);
-              mutGraphCells.set(
-                cellKey,
-                mutGraphCells.get(cellKey, Immutable.Set<Term>()).add(q.object)
-              );
-            },
-            subj,
-            null,
-            null,
-            null
-          );
-        });
-      }
-    );
-    this.preds = Array.from(preds);
-    this.preds.splice(this.preds.indexOf(rdf.type), 1);
-    // also pull out label, which should be used on 1st column
-    this.preds.sort();
-  }
-
-  makeCellKey(subj: NamedNode, pred: NamedNode): ISP {
-    return SP({
-      subj: subj,
-      pred: pred,
-    });
-  }
-}