Public
Edited
Dec 27, 2022
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class Slot {
constructor(name) {
this.name = name;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
class DataPath {
// --
// Patches the given `data` structure with the `value` at the given `path`.
// When `value` is `null`, the element will be deleted.
static Apply(data, value, path = []) {
if (path.length == 0) {
return value;
}
data =
data === undefined
? path && path.length && typeof path[0] === "number"
? []
: {}
: data;
let scope = data;
let n = path.length;
let k = undefined;
let i = 0;
for (; i < n - 1; i++) {
if (scope[(k = path[i])] === undefined) {
const w = typeof path[i + 1] === "number" ? [] : {};
if (typeof k === "number" && scope instanceof Array) {
while (scope.length < k) {
scope.push(undefined);
}
}
scope = scope[k] = w;
} else {
scope = scope[k];
}
}
k = path[i];
if (value === null) {
if (typeof k === "number" && scope instanceof Array) {
scope.splice(k, 1);
} else {
delete scope[k];
}
} else {
scope[k] = value;
}
return data;
}

static Extract(data, path = []) {
let scope = data;
for (let k of path) {
if (!scope) {
return undefined;
} else if (scope instanceof Node) {
// NOTE: If 'k' is a string, then it's an attribute, but we don't always
// have an attribute node, for instance the `style` node has none.
scope =
typeof k === "string"
? k === "style"
? scope[k]
: scope.getAttributeNode(k)
: scope.childNodes[k];
} else {
scope = scope[k];
}
}
return scope;
}

constructor(path) {
this.path = path;
}

apply(data, value) {
return DataPath.Apply(data, value, this.path);
}
extract(data) {
return DataPath.Extract(data, this.path);
}
}
Insert cell
Insert cell
Insert cell
Insert cell
{
const user = new Slot("user");
const name = new Slot("name");
const User = new DataTemplate({ user, name });
return User.extract({ user: "JohnSmith", name: "John H. Smith III" });
}
Insert cell
class DataTemplate {
static RE_NUMBER;
static RAW_OBJECT = Object.getPrototypeOf({});

// --
// Utility function to get the type of the given value
static TypeOf(value) {
const t = value === null ? "null" : typeof value;
return t !== "object"
? t
: value instanceof Array
? "array"
: value instanceof Slot
? "slot"
: Object.getPrototypeOf(value) === DataTemplate.RAW_OBJECT
? "rawobject"
: t;
}

// --
// Walks the given `value`, calling `callback(value, path, isSlot)`
static Walk(value, callback, path = []) {
if (value === undefined || value === null) {
callback(value, path, false);
} else if (value instanceof Slot) {
callback(value, path, true);
} else if (value instanceof Array) {
for (let k = 0; k < value.length; k++) {
DataTemplate.Walk(
value[k],
callback,
path && path.length ? [...path, k] : [k]
);
}
} else if (
typeof value === "object" &&
Object.getPrototypeOf(value) === DataTemplate.RAW_OBJECT
) {
for (let k in value) {
DataTemplate.Walk(
value[k],
callback,
path && path.length ? [...path, k] : [k]
);
}
}
}

// --
// Returns a `Map[Slot,Path]` with the holes contained in the given template
// data structure.
static Holes(template) {
const res = new Map();
DataTemplate.Walk(
template,
(value, path, isSlot) => isSlot && res.set(value, new DataPath(path))
);
return res;
}

// --
// Matches slots defined in `template` with corresponding values in `value`. The
// extraction is a loose matching, in the sense that if the template contains
// non-slot values, they will be ignored.
static Extract(template, value, matched = new Map()) {
if (template == undefined || template == null || value === undefined) {
return matched;
} else if (template instanceof Slot) {
matched.set(template, value);
return matched;
} else if (value === undefined || value === null) {
return matched;
} else if (template instanceof Array) {
for (let k = 0; k < template.length; k++) {
DataTemplate.Match(template[k], value[k], matched);
}
} else if (
typeof value === "object" &&
Object.getPrototypeOf(value) === DataTemplate.RAW_OBJECT
) {
for (let k in template) {
DataTemplate.Match(template[k], value[k], matched);
}
}
return matched;
}

static Match(template, value, matched = new Map()) {
const tt = DataTemplate.TypeOf(template);
// If it's a slot, we match right away
if (tt === "slot") {
matched.set(template, value);
return matched;
}
// Otherwise we match against the value
const tv = DataTemplate.TypeOf(value);
console.log({ tv, tt, template, value, matched });
if (tt !== tv) {
// If the value is different, we exit early
return false;
} else {
switch (tt) {
case "array":
case "rawobject":
for (let i in template) {
if (DataTemplate.Match(template[i], value[i], matched) === false) {
return false;
}
}
return matched;
default:
return template == value ? matched : false;
}
}
}

constructor(template) {
this.template = template;
// Types:
// (0) empty
// (1) literal value (could be an object)
// (2) Slot
// (3) Array
// (4) (Raw) Object
this.type =
template === null || template === undefined
? 0
: template instanceof Object
? template instanceof Array
? 3
: template instanceof Slot
? 2
: Object.getPrototypeOf(template) === DataTemplate.RAW_OBJECT
? 4
: 1
: 1;
// Holes is a Map[Slot,Path]
this.holes = DataTemplate.Holes(template);
this.pattern = undefined;
for (let [slot, path] of this.holes.entries()) {
this.pattern = path.apply(this.pattern, slot);
}
}

extract(value) {
return DataTemplate.Extract(this.pattern, value);
}

match(value) {
return DataTemplate.Match(this.template, value);
}

// --
// Applies the given mapping (`Map[Slot,Any]`) to this template, returning a new template
// instance.
apply(mapping) {
switch (this.type) {
case 0:
case 1:
return this.template;
case 2:
return mapping.get(this.holes[0]);
case 3:
case 4:
const res = this.type === 3 ? [...this.template] : { ...this.template };
for (const [slot, path] of this.holes.entries()) {
path.apply(res, mapping.get(slot));
}
return res;
default:
onError(`DataTemplate.apply(): Unsupported type ${this.type}`, {
type: this.type,
mapping
});
}
}
}
Insert cell
Insert cell
Insert cell
Insert cell
template = (data) => new DataTemplate(data)
Insert cell
slot = (name) => new Slot(name)
Insert cell
{
const t = template([1, 2, slot("3"), { x: slot("x"), y: 10 }]);
console.log("---");
return {
extract: t.extract([1, 2, 3, { x: 30, y: 10 }]),
matchOK1: t.match([1, 2, 3, { x: 30, y: 10 }]),
matchOK2: t.match([1, 2, 3, { x: 30, y: 10, z: 12312 }]),
matchFail: t.match([1, 2, 3, { x: 30, y: 30 }])
};
}
Insert cell
{
const name = slot();
const t = template({ name });
return t.extract({ name: "John" }).get(name);
return t.apply(t.extract({ name: "John" }));
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more