view src/view_loader.ts @ 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
line wrap: on
line source

// Load requested view and provide access to it
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();
  const res = await fetch(url);
  const body = await res.text();
  const parser = new Parser({ format: "N3" });
  await new Promise((done, rej) => {
    parser.parse(body, (err, quad, prefixes) => {
      if (err) {
        throw err;
      }
      if (quad) {
        store.addQuad(quad);
      } else {
        done(null);
      }
    });
  });
  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) {
        fetchAndParse(url).then((s2) => {
          this.graph = s2;
          res(null);
        });
      } else {
        res(null);
      }
    });
    this.ready.then(() => {
      this.viewRoot = firstElem(
        this.graph.getSubjects(RDF("type"), EX("View"), null)
      ) as NamedNode;
    });
  }
  label() {
    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;
  }
}