mirror of
https://github.com/nlohmann/json.git
synced 2026-10-29 00:55:44 +00:00
deploy: a47170346a
This commit is contained in:
1 parent
b36f8a9821
commit
11a863958e
1180 files changed
+14211
-6025
No files matched your search
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Marks links that leave the site with the class "nj-external", which brand.css turns into a small icon in front of
|
||||
* the link. A link is external when its host is neither the current one nor the production site. Links in the page
|
||||
* actions (edit, raw), buttons, header anchors, and links that only wrap an image (badges) are left alone.
|
||||
* Runs through Material's document$ (navigation.instant), so it also applies after an instant page switch.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function mark() {
|
||||
var links = document.querySelectorAll(".md-typeset a[href]");
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var a = links[i];
|
||||
if (a.hostname === "" || a.hostname === location.hostname || a.hostname === "json.nlohmann.me") {
|
||||
continue;
|
||||
}
|
||||
if (a.protocol !== "http:" && a.protocol !== "https:") {
|
||||
continue;
|
||||
}
|
||||
if (a.matches(".md-button, .md-content__button, .headerlink") || a.querySelector("img, svg")) {
|
||||
continue;
|
||||
}
|
||||
a.classList.add("nj-external");
|
||||
}
|
||||
}
|
||||
|
||||
if (window.document$ && window.document$.subscribe) {
|
||||
window.document$.subscribe(mark);
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", mark);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,106 @@
|
||||
/* figures: charts. Number literal explorer on features/types/number_handling.md.
|
||||
The table in .nj-charts-num is the source and the no-JS fallback; its rows were verified by compiling a program
|
||||
against include/ (json::parse + type() + dump()). The explorer only looks literals up in that table: it does not
|
||||
parse numbers itself, so anything else is reported as "not in the verified list". */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var WHY = {
|
||||
"0": "No fraction, exponent, or minus sign: stored as unsigned integer.",
|
||||
"-0": "Leading minus sign: stored as signed integer; the serialization does not reproduce the -.",
|
||||
"-0.0": "Has a fraction: stored as double, which keeps the sign of zero.",
|
||||
"1E3": "Has an exponent: always stored as double.",
|
||||
"-12345678912345789123456789": "Smaller than INT64_MIN: does not fit std::int64_t, so it is stored as double.",
|
||||
"18446744073709551615": "2^64-1 is the largest value std::uint64_t can store without loss.",
|
||||
"18446744073709551616": "2^64 does not fit std::uint64_t, so it is stored as double.",
|
||||
"-9223372036854775808": "-2^63 is the smallest value std::int64_t can store.",
|
||||
"1E400": "Its rounded value is not finite as double: parsing throws out_of_range.406.",
|
||||
"1E-400": "Too small for double: becomes zero, with the sign of the number.",
|
||||
"3.141592653589793238462643383279": "Rounded to the nearest double, then printed with the fewest digits that read back.",
|
||||
"2555.5599999999999": "The fewest digits that read back as the same double are 2555.56.",
|
||||
"0.0000972439793401814": "The %g layout switches to scientific notation.",
|
||||
"12345E-5": "The %g layout prints this double without exponent."
|
||||
};
|
||||
|
||||
function init() {
|
||||
var roots = document.querySelectorAll(".nj-charts-num");
|
||||
Array.prototype.forEach.call(roots, setup);
|
||||
}
|
||||
|
||||
function setup(root, idx) {
|
||||
if (root.dataset.njChartsReady) return;
|
||||
var table = root.querySelector("table");
|
||||
if (!table) return;
|
||||
root.dataset.njChartsReady = "1";
|
||||
|
||||
var entries = [];
|
||||
Array.prototype.forEach.call(table.querySelectorAll("tbody tr"), function (tr) {
|
||||
var c = tr.querySelectorAll("td");
|
||||
if (c.length < 3) return;
|
||||
entries.push({ lit: c[0].textContent.trim(), type: c[1].textContent.trim(), dump: c[2].textContent.trim(), row: tr });
|
||||
});
|
||||
if (!entries.length) return;
|
||||
|
||||
var id = "nj-charts-num-" + idx;
|
||||
var ui = document.createElement("div");
|
||||
ui.className = "nj-charts-num-ui";
|
||||
ui.innerHTML =
|
||||
'<ul class="nj-charts-num-chips" aria-label="Literals to show"></ul>' +
|
||||
'<div id="' + id + '-out" aria-live="polite"></div>';
|
||||
root.insertBefore(ui, root.firstChild);
|
||||
|
||||
var list = ui.querySelector("ul");
|
||||
var out = ui.querySelector("#" + id + "-out");
|
||||
var buttons = [];
|
||||
|
||||
entries.forEach(function (e) {
|
||||
var li = document.createElement("li");
|
||||
var b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.className = "nj-charts-num-chip";
|
||||
b.textContent = e.lit;
|
||||
b.setAttribute("aria-pressed", "false");
|
||||
b.setAttribute("aria-label", "Show literal " + e.lit);
|
||||
b.addEventListener("click", function () { show(e.lit); });
|
||||
li.appendChild(b);
|
||||
list.appendChild(li);
|
||||
buttons.push(b);
|
||||
});
|
||||
|
||||
function show(text) {
|
||||
var lit = text.trim();
|
||||
var hit = null;
|
||||
entries.forEach(function (e, i) {
|
||||
var on = e.lit === lit;
|
||||
if (on) hit = e;
|
||||
buttons[i].setAttribute("aria-pressed", on ? "true" : "false");
|
||||
e.row.classList.toggle("nj-charts-num-hit", on);
|
||||
});
|
||||
out.textContent = "";
|
||||
if (hit) {
|
||||
var dl = document.createElement("dl");
|
||||
dl.className = "nj-charts-num-out";
|
||||
[["literal", hit.lit, true], ["stored as", hit.type, false], ["dump()", hit.dump, true],
|
||||
["why", WHY[hit.lit] || "", false]].forEach(function (p) {
|
||||
if (!p[1]) return;
|
||||
var dt = document.createElement("dt"); dt.textContent = p[0];
|
||||
var dd = document.createElement("dd");
|
||||
if (p[2] && p[1].indexOf("(") !== 0) { var c = document.createElement("code"); c.textContent = p[1]; dd.appendChild(c); }
|
||||
else dd.textContent = p[1];
|
||||
dl.appendChild(dt); dl.appendChild(dd);
|
||||
});
|
||||
out.appendChild(dl);
|
||||
}
|
||||
}
|
||||
|
||||
show(entries[1] ? entries[1].lit : entries[0].lit);
|
||||
}
|
||||
|
||||
if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") {
|
||||
document$.subscribe(init);
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1 @@
|
||||
/* figures: exceptions */
|
||||
@@ -0,0 +1,128 @@
|
||||
/* figures: integration
|
||||
*
|
||||
* Namespace name builder on features/namespace.md. The page ships a static strip for version 3.11.2 with
|
||||
* JSON_DIAGNOSTICS=1; this script adds a version selector and one checkbox per ABI-affecting macro and rebuilds the
|
||||
* strip. Tags, their order, and the versions that know them follow include/nlohmann/detail/abi_macros.hpp and the
|
||||
* "Version history" section of the page; a tag that a version does not have cannot be ticked.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var VERSIONS = ["3.11.2", "3.11.3", "3.12.0", "3.13.0"];
|
||||
// macro, tag, first version with the tag, true if any definition counts (#ifdef) instead of a non-zero value
|
||||
var TAGS = [
|
||||
["JSON_DIAGNOSTICS", "_diag", "3.11.2"],
|
||||
["JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON", "_ldvcmp", "3.11.2"],
|
||||
["JSON_DIAGNOSTIC_POSITIONS", "_dp", "3.12.0"],
|
||||
["JSON_BRACE_INIT_COPY_SEMANTICS", "_bics", "3.13.0"],
|
||||
["JSON_PRECISE_STREAM_POSITION", "_psp", "3.13.0"],
|
||||
["JSON_STRICT_NUL_HANDLING", "_snul", "3.13.0"],
|
||||
["JSON_STRICT_BINARY_UTF8", "_sbu8", "3.13.0"],
|
||||
["JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS", "_ekmo", "3.13.0"],
|
||||
["JSON_DISABLE_STREAM_BULK_SCAN", "_dsbs", "3.13.0"],
|
||||
["JSON_DISABLE_UNLOCKED_FILE_IO", "_dufio", "3.13.0"],
|
||||
["JSON_NO_SIMD", "_ns", "3.13.0", true]
|
||||
];
|
||||
var NO_VERSION = "NLOHMANN_JSON_NAMESPACE_NO_VERSION";
|
||||
|
||||
function el(tag, cls, text) {
|
||||
var node = document.createElement(tag);
|
||||
if (cls) { node.className = cls; }
|
||||
if (text !== undefined) { node.textContent = text; }
|
||||
return node;
|
||||
}
|
||||
|
||||
function seg(kind, code, note) {
|
||||
var span = el("span", "nj-integration-ns__seg nj-integration-ns__seg--" + kind);
|
||||
span.appendChild(el("code", "", code));
|
||||
var small = el("small");
|
||||
// allow line breaks after underscores so that long macro names stay narrow
|
||||
(note || "\u00a0").split("_").forEach(function (part, i, all) {
|
||||
small.appendChild(document.createTextNode(part + (i < all.length - 1 ? "_" : "")));
|
||||
if (i < all.length - 1) { small.appendChild(el("wbr")); }
|
||||
});
|
||||
span.appendChild(small);
|
||||
return span;
|
||||
}
|
||||
|
||||
function setup(fig) {
|
||||
if (fig.getAttribute("data-nj-ready")) { return; }
|
||||
fig.setAttribute("data-nj-ready", "1");
|
||||
var strip = fig.querySelector(".nj-integration-ns__strip");
|
||||
var caption = fig.querySelector("figcaption");
|
||||
var uid = "nj-integration-ns-" + Math.random().toString(36).slice(2, 8);
|
||||
var form = el("form", "nj-integration-ns__controls");
|
||||
form.setAttribute("aria-label", "Namespace name builder");
|
||||
form.addEventListener("submit", function (e) { e.preventDefault(); });
|
||||
|
||||
var vlabel = el("label", "nj-integration-ns__version", "Library version ");
|
||||
var select = el("select");
|
||||
VERSIONS.forEach(function (v) {
|
||||
var option = el("option", "", v);
|
||||
option.value = v;
|
||||
option.selected = v === "3.11.2";
|
||||
select.appendChild(option);
|
||||
});
|
||||
vlabel.appendChild(select);
|
||||
form.appendChild(vlabel);
|
||||
|
||||
var list = el("fieldset", "nj-integration-ns__list");
|
||||
list.appendChild(el("legend", "", "ABI-affecting macros, in tag order"));
|
||||
var boxes = TAGS.concat([[NO_VERSION, "", "3.11.2"]]).map(function (t, i) {
|
||||
var label = el("label", "nj-integration-ns__opt");
|
||||
var box = el("input");
|
||||
box.type = "checkbox";
|
||||
box.id = uid + "-" + i;
|
||||
box.checked = t[0] === "JSON_DIAGNOSTICS";
|
||||
label.htmlFor = box.id;
|
||||
label.appendChild(box);
|
||||
label.appendChild(el("code", "", t[0] + (t[3] ? "" : "=1")));
|
||||
var meta = el("span", "nj-integration-ns__meta");
|
||||
meta.appendChild(el("span", "nj-integration-ns__hint",
|
||||
t[0] === NO_VERSION ? "drops the version" : (t[3] ? "defined: " : "") + t[1]));
|
||||
meta.appendChild(el("span", "nj-integration-ns__since", "since " + t[2]));
|
||||
label.appendChild(meta);
|
||||
list.appendChild(label);
|
||||
return { def: t, box: box, label: label };
|
||||
});
|
||||
form.appendChild(list);
|
||||
fig.insertBefore(form, caption);
|
||||
|
||||
function update() {
|
||||
var version = select.value, index = VERSIONS.indexOf(version);
|
||||
var parts = [seg("root", "nlohmann", "root namespace"), seg("sep", "::"), seg("abi", "json_abi", "always")];
|
||||
var noVersion = false, defs = [];
|
||||
boxes.forEach(function (b) {
|
||||
var available = VERSIONS.indexOf(b.def[2]) <= index;
|
||||
b.box.disabled = !available;
|
||||
if (!available) { b.box.checked = false; }
|
||||
b.label.classList.toggle("is-unavailable", !available);
|
||||
if (!b.box.checked) { return; }
|
||||
defs.push(b.def[0] + (b.def[3] ? "" : "=1"));
|
||||
if (b.def[0] === NO_VERSION) { noVersion = true; } else { parts.push(seg("tag", b.def[1], b.def[0] + (b.def[3] ? "" : "=1"))); }
|
||||
});
|
||||
if (!noVersion) { parts.push(seg("ver", "_v" + version.replace(/\./g, "_"), "version " + version)); }
|
||||
strip.textContent = "";
|
||||
parts.forEach(function (p) { strip.appendChild(p); });
|
||||
strip.setAttribute("aria-label", "Resulting namespace: nlohmann::" + parts.slice(2).map(function (p) {
|
||||
return p.firstChild.textContent;
|
||||
}).join(""));
|
||||
caption.textContent = "Version " + version + (defs.length === 0 ? " with no ABI-affecting macro defined" :
|
||||
defs.length > 3 ? " with " + defs.length + " of the macros defined" : " with " + defs.join(", ")) + ".";
|
||||
}
|
||||
form.addEventListener("change", update);
|
||||
update();
|
||||
}
|
||||
|
||||
function init() {
|
||||
Array.prototype.forEach.call(document.querySelectorAll("[data-nj-integration-ns]"), setup);
|
||||
}
|
||||
|
||||
if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") {
|
||||
document$.subscribe(init);
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,78 @@
|
||||
/* figures: jsonview -- stepper for the edit frames on features/json_view.md.
|
||||
Without JS the frames (.nj-jsonview-step) show one below the other; with JS one frame at a time, with
|
||||
previous/next and one button per step. Never advances on its own. */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function njJsonviewInit(root) {
|
||||
if (root.classList.contains("is-enhanced")) return;
|
||||
var steps = Array.prototype.slice.call(root.querySelectorAll(".nj-jsonview-step"));
|
||||
if (steps.length < 2) return;
|
||||
var current = 0;
|
||||
|
||||
var bar = document.createElement("div");
|
||||
bar.className = "nj-jsonview-controls";
|
||||
bar.setAttribute("role", "group");
|
||||
bar.setAttribute("aria-label", "Edit steps");
|
||||
|
||||
function button(label, aria, onClick) {
|
||||
var b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.textContent = label;
|
||||
b.setAttribute("aria-label", aria);
|
||||
b.addEventListener("click", onClick);
|
||||
bar.appendChild(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
var prev = button("‹ Back", "Previous step", function () { show(current - 1, true); });
|
||||
var dots = steps.map(function (s, i) {
|
||||
return button(String(i + 1), "Step " + (i + 1) + ": " + (s.getAttribute("data-label") || ""), function () { show(i, true); });
|
||||
});
|
||||
var next = button("Next ›", "Next step", function () { show(current + 1, true); });
|
||||
var status = document.createElement("span");
|
||||
status.className = "nj-jsonview-status";
|
||||
status.setAttribute("aria-live", "polite");
|
||||
bar.appendChild(status);
|
||||
|
||||
function show(i, focusKeep) {
|
||||
current = Math.max(0, Math.min(steps.length - 1, i));
|
||||
steps.forEach(function (s, k) {
|
||||
s.classList.toggle("is-current", k === current);
|
||||
s.setAttribute("aria-hidden", k === current ? "false" : "true");
|
||||
});
|
||||
dots.forEach(function (d, k) {
|
||||
if (k === current) d.setAttribute("aria-current", "step"); else d.removeAttribute("aria-current");
|
||||
});
|
||||
prev.disabled = current === 0;
|
||||
next.disabled = current === steps.length - 1;
|
||||
status.textContent = "Step " + (current + 1) + " of " + steps.length;
|
||||
if (focusKeep && document.activeElement && document.activeElement.disabled) {
|
||||
(current === 0 ? next : prev).focus();
|
||||
}
|
||||
}
|
||||
|
||||
root.addEventListener("keydown", function (e) {
|
||||
if (e.target.tagName !== "BUTTON") return;
|
||||
if (e.key === "ArrowRight") { show(current + 1, true); e.preventDefault(); }
|
||||
else if (e.key === "ArrowLeft") { show(current - 1, true); e.preventDefault(); }
|
||||
});
|
||||
|
||||
root.insertBefore(bar, root.firstChild);
|
||||
root.classList.add("is-enhanced");
|
||||
show(0, false);
|
||||
}
|
||||
|
||||
function njJsonviewAll() {
|
||||
var roots = document.querySelectorAll("[data-nj-jsonview-steps]");
|
||||
for (var i = 0; i < roots.length; i++) njJsonviewInit(roots[i]);
|
||||
}
|
||||
|
||||
if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") {
|
||||
document$.subscribe(njJsonviewAll);
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", njJsonviewAll);
|
||||
} else {
|
||||
njJsonviewAll();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,585 @@
|
||||
/* figures: macro chooser for the NLOHMANN_DEFINE_TYPE_* / NLOHMANN_DEFINE_DERIVED_TYPE_* macros.
|
||||
Usage: <div class="nj-macro-chooser" data-variant="full|compact" data-preset="base=yes,keys=custom" markdown>
|
||||
with a static fallback list inside (shown without JavaScript). Links marked with { data-nj-mc="KEY" } in the
|
||||
fallback are reused for the reference links, so each embedding page resolves its own relative URLs. Keys:
|
||||
intrusive, non_intrusive, derived, with_names, third_party, non_default.
|
||||
The rules follow include/nlohmann/detail/macro_scope.hpp: WITH_NAMES takes "key", member pairs and no empty
|
||||
member list; WITH_DEFAULT and the plain variants default-construct the type in from_json/get; ONLY_SERIALIZE
|
||||
generates to_json only; a type that is not default constructible and converts both ways gets no macro but an
|
||||
adl_serializer specialization whose from_json returns the value; DERIVED_TYPE calls the base type's to_json/from_json first; at most 63 members (31 with
|
||||
WITH_NAMES). The generated programs were compiled and run with clang++ -std=c++11 against include/. */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var QUESTIONS = [
|
||||
{ id: "edit", legend: "Can you edit the class?", options: [["yes", "Yes"], ["no", "No, it is third-party"]] },
|
||||
{ id: "access", legend: "Member access", options: [["public", "All public"], ["private", "Some private"]] },
|
||||
{ id: "base", legend: "Base class", options: [["no", "None"], ["yes", "Derived from a base"]] },
|
||||
{ id: "dir", legend: "Direction", options: [["both", "To and from JSON"], ["to", "Only to JSON"]] },
|
||||
{ id: "missing", legend: "Missing keys when parsing", options: [["throw", "Throw"], ["default", "Keep defaults"]] },
|
||||
{ id: "keys", legend: "JSON keys", options: [["members", "Member names"], ["custom", "Custom names"]] },
|
||||
{ id: "ctor", legend: "Default constructible?", options: [["yes", "Yes"], ["no", "No"]] }
|
||||
];
|
||||
|
||||
var KEYWORDS = ("alignas alignof and asm auto bool break case catch char class const constexpr continue decltype default " +
|
||||
"delete do double else enum explicit export extern false float for friend goto if inline int long mutable namespace " +
|
||||
"new noexcept not nullptr operator or private protected public register return short signed sizeof static " +
|
||||
"struct switch template this throw true try typedef typeid typename union unsigned using virtual void volatile " +
|
||||
"while").split(" ");
|
||||
var HL_TYPES = ["int", "bool", "double", "float", "char", "void", "auto", "unsigned", "long", "short", "signed"];
|
||||
var HL_CONST = ["true", "false", "nullptr"];
|
||||
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
||||
var MEMBER_TYPE = /^[A-Za-z_][A-Za-z0-9_:<>, ]*$/;
|
||||
|
||||
var counter = 0;
|
||||
|
||||
function el(tag, attrs, text) {
|
||||
var e = document.createElement(tag);
|
||||
if (attrs) {
|
||||
Object.keys(attrs).forEach(function (k) { e.setAttribute(k, attrs[k]); });
|
||||
}
|
||||
if (text !== undefined) e.textContent = text;
|
||||
return e;
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
/* small C++ tokenizer emitting the Pygments classes the site already colours */
|
||||
function highlight(code) {
|
||||
var out = "";
|
||||
var re = /(\/\/[^\n]*)|(#include)|(<[a-z_/.]+\.hpp>|<[a-z]+>)|("(?:[^"\\\n]|\\.)*")|(\b\d+\b)|([A-Za-z_][A-Za-z0-9_]*)|(::|<<|==|[=:<>&])|([{}()\[\];,.!])|(\s+)|(.)/g;
|
||||
var m, prev = "";
|
||||
while ((m = re.exec(code)) !== null) {
|
||||
var t = m[0];
|
||||
if (m[1]) out += '<span class="c1">' + esc(t) + "</span>";
|
||||
else if (m[2]) out += '<span class="cp">' + esc(t) + "</span>";
|
||||
else if (m[3] && prev === "#include") out += '<span class="cpf">' + esc(t) + "</span>";
|
||||
else if (m[4]) out += '<span class="s">' + esc(t) + "</span>";
|
||||
else if (m[5]) out += '<span class="mi">' + esc(t) + "</span>";
|
||||
else if (m[6]) {
|
||||
var cls = "n";
|
||||
if (HL_CONST.indexOf(t) >= 0) cls = "kc";
|
||||
else if (HL_TYPES.indexOf(t) >= 0) cls = "kt";
|
||||
else if (KEYWORDS.indexOf(t) >= 0) cls = "k";
|
||||
else if (prev === "namespace") cls = "nn";
|
||||
else if (prev === "struct" || prev === "class") cls = "nc";
|
||||
out += '<span class="' + cls + '">' + esc(t) + "</span>";
|
||||
} else if (m[7]) out += '<span class="o">' + esc(t) + "</span>";
|
||||
else if (m[8]) out += '<span class="p">' + esc(t) + "</span>";
|
||||
else out += esc(t);
|
||||
if (!m[9]) prev = (m[3] && prev === "#include") ? "" : t;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function parsePreset(s) {
|
||||
var r = {};
|
||||
(s || "").split(",").forEach(function (kv) {
|
||||
var p = kv.split("=");
|
||||
if (p.length === 2) r[p[0].trim()] = p[1].trim();
|
||||
});
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ---- the decision logic, independent of the DOM ---- */
|
||||
function decide(st) {
|
||||
var res = { errors: [], warnings: [], macro: null, reason: "", place: "", ref: null };
|
||||
var n = st.members.length;
|
||||
var names = st.keys === "custom";
|
||||
|
||||
if (st.edit === "no" && st.access === "private") {
|
||||
res.errors.push({ text: "No macro can reach private members from outside the class. Write to_json/from_json by hand using the public interface, for example in a specialization of adl_serializer.", link: "third_party", linkText: "How do I convert third-party types?" });
|
||||
}
|
||||
var adl = st.dir === "both" && st.ctor === "no";
|
||||
if (!adl && names && n === 0) {
|
||||
res.errors.push({ text: "The WITH_NAMES macros need at least one key and member pair. Without members there is nothing to rename: choose Member names." });
|
||||
}
|
||||
var limit = names ? 31 : 63;
|
||||
if (!adl && n > limit) {
|
||||
res.errors.push({ text: "The macros support at most " + limit + " members" + (names ? " with custom names" : "") + ". Write to_json/from_json by hand for larger types." });
|
||||
}
|
||||
|
||||
/* identifier checks */
|
||||
if (!IDENT.test(st.type) || KEYWORDS.indexOf(st.type) >= 0) res.errors.push({ text: "The type name must be a C++ identifier." });
|
||||
if (st.base === "yes" && (!IDENT.test(st.baseName) || KEYWORDS.indexOf(st.baseName) >= 0)) res.errors.push({ text: "The base class name must be a C++ identifier." });
|
||||
if (st.base === "yes" && st.baseName === st.type) res.errors.push({ text: "The base class needs a different name than the type." });
|
||||
var seenName = {}, seenKey = {};
|
||||
st.members.forEach(function (mb, i) {
|
||||
var label = "Member " + (i + 1);
|
||||
if (!IDENT.test(mb.name) || KEYWORDS.indexOf(mb.name) >= 0) res.errors.push({ text: label + ": the name must be a C++ identifier." });
|
||||
else if (seenName[mb.name]) res.errors.push({ text: label + ": the name " + mb.name + " is used twice." });
|
||||
else if (mb.name === st.type) res.errors.push({ text: label + ": a member cannot have the name of its class." });
|
||||
seenName[mb.name] = true;
|
||||
if (!MEMBER_TYPE.test(mb.mtype.trim())) res.errors.push({ text: label + ": enter a type such as int, std::string or std::vector<int>." });
|
||||
var key = names ? mb.key : mb.name;
|
||||
if (seenKey[key]) res.warnings.push(label + ": the JSON key \"" + key + "\" is used twice; the later member overwrites the earlier one in to_json.");
|
||||
seenKey[key] = true;
|
||||
if (st.base === "yes" && key === "id") res.warnings.push(label + ": the key \"id\" is also written by the example base class; the derived member overwrites it.");
|
||||
});
|
||||
|
||||
if (st.edit === "no" && st.access === "private") return res;
|
||||
|
||||
if (adl) {
|
||||
res.adl = true;
|
||||
res.intrusive = st.access === "private";
|
||||
res.ref = "non_default";
|
||||
res.reason = "Every macro that generates from_json default-constructs the type, so none fits. A specialization of adl_serializer returns the value from from_json instead" +
|
||||
(st.access === "private" ? "; the class befriends it to reach the private members" : "") + ".";
|
||||
res.place = "Write the specialization in namespace nlohmann, after the class.";
|
||||
return res;
|
||||
}
|
||||
|
||||
var intrusive = st.access === "private";
|
||||
var parts = ["NLOHMANN_DEFINE", st.base === "yes" ? "DERIVED_TYPE" : "TYPE", intrusive ? "INTRUSIVE" : "NON_INTRUSIVE"];
|
||||
if (st.dir === "to") parts.push("ONLY_SERIALIZE");
|
||||
else if (st.missing === "default") parts.push("WITH_DEFAULT");
|
||||
if (names) parts.push("WITH_NAMES");
|
||||
res.macro = parts.join("_");
|
||||
res.intrusive = intrusive;
|
||||
res.ref = names ? "with_names" : (st.base === "yes" ? "derived" : (intrusive ? "intrusive" : "non_intrusive"));
|
||||
|
||||
var why = [];
|
||||
if (intrusive) why.push("Private members need the macro inside the class, where its friend functions can access them");
|
||||
else if (st.edit === "no") why.push("You cannot edit the class, so the macro goes next to it in its namespace, which works because all members are public");
|
||||
else why.push("All members are public, so the macro can stay outside the class, in its namespace");
|
||||
if (st.base === "yes") why.push("the conversion of " + (st.baseName || "the base") + " runs first");
|
||||
if (st.dir === "to") why.push("only to_json is generated");
|
||||
else if (st.missing === "default") why.push("missing keys keep the value of a default-constructed " + st.type);
|
||||
else why.push("a missing key throws out_of_range.403");
|
||||
if (names) why.push("each member is preceded by its JSON key");
|
||||
res.reason = why.join("; ") + ".";
|
||||
res.place = intrusive ? "Write the macro inside the class body." : "Write the macro after the class, in the same namespace.";
|
||||
if (st.dir === "to" && st.ctor === "no") res.warnings.push("The type is not default constructible: fine for ONLY_SERIALIZE, but j.get<" + st.type + ">() will not compile.");
|
||||
return res;
|
||||
}
|
||||
|
||||
/* C++ string literal; control characters as three-digit octal escapes, which cannot run into the next character */
|
||||
function cppString(s) {
|
||||
return '"' + s.replace(/[\\"\u0000-\u001f\u007f]/g, function (c) {
|
||||
if (c === "\\" || c === '"') return "\\" + c;
|
||||
var o = c.charCodeAt(0).toString(8);
|
||||
return "\\" + ("00" + o).slice(-3);
|
||||
}) + '"';
|
||||
}
|
||||
|
||||
function macroCall(name, head, pairs, indent) {
|
||||
var one = name + "(" + head.concat(pairs).join(", ") + ")";
|
||||
if (one.length + indent.length <= 88) return indent + one;
|
||||
var lines = [indent + name + "(" + head.join(", ") + (pairs.length ? "," : ")")];
|
||||
pairs.forEach(function (p, i) {
|
||||
lines.push(indent + " " + p + (i === pairs.length - 1 ? ")" : ","));
|
||||
});
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function generate(st, res) {
|
||||
var ns = "ns";
|
||||
var T = st.type, B = st.baseName;
|
||||
var priv = res.intrusive;
|
||||
var names = st.keys === "custom";
|
||||
var suffix = st.dir === "to" ? "_ONLY_SERIALIZE" : (st.missing === "default" ? "_WITH_DEFAULT" : "");
|
||||
var adl = !!res.adl;
|
||||
var noCtor = st.ctor === "no" && (st.members.length > 0 || st.base === "yes");
|
||||
var L = [];
|
||||
L.push("#include <iostream>");
|
||||
L.push("#include <nlohmann/json.hpp>");
|
||||
L.push("");
|
||||
L.push("using nlohmann::json;");
|
||||
L.push("");
|
||||
L.push("namespace " + ns);
|
||||
L.push("{");
|
||||
if (st.base === "yes") {
|
||||
L.push("// stand-in for your base class: it must be serializable itself");
|
||||
if (priv) {
|
||||
L.push("class " + B);
|
||||
L.push("{");
|
||||
L.push(" private:");
|
||||
L.push(" int id{};");
|
||||
L.push("");
|
||||
L.push(" public:");
|
||||
L.push(" NLOHMANN_DEFINE_TYPE_INTRUSIVE" + suffix + "(" + B + ", id)");
|
||||
L.push("};");
|
||||
} else {
|
||||
L.push("struct " + B);
|
||||
L.push("{");
|
||||
L.push(" int id{};");
|
||||
L.push("};");
|
||||
L.push("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE" + suffix + "(" + B + ", id)");
|
||||
}
|
||||
L.push("");
|
||||
}
|
||||
var kw = priv ? "class " : "struct ";
|
||||
var inherit = st.base === "yes" ? (priv ? " : public " + B : " : " + B) : "";
|
||||
L.push(kw + T + inherit);
|
||||
L.push("{");
|
||||
if (priv) L.push(" private:");
|
||||
st.members.forEach(function (mb) {
|
||||
L.push(" " + mb.mtype.trim() + " " + mb.name + (noCtor ? ";" : "{};"));
|
||||
});
|
||||
var head = [T];
|
||||
if (st.base === "yes") head.push(B);
|
||||
var pairs = st.members.map(function (mb) {
|
||||
return names ? cppString(mb.key) + ", " + mb.name : mb.name;
|
||||
});
|
||||
if (noCtor || priv) {
|
||||
if (st.members.length) L.push("");
|
||||
if (priv) L.push(" public:");
|
||||
}
|
||||
if (noCtor) {
|
||||
var plist = st.members.map(function (mb) { return mb.mtype.trim() + " " + mb.name + "_"; });
|
||||
var ilist = st.members.map(function (mb) { return mb.name + "(" + mb.name + "_)"; });
|
||||
if (st.base === "yes") {
|
||||
plist.unshift(B + " base_");
|
||||
ilist.unshift(B + "(base_)");
|
||||
}
|
||||
L.push(" " + T + "(" + plist.join(", ") + ")");
|
||||
L.push(" : " + ilist.join(", "));
|
||||
L.push(" {}");
|
||||
if (priv) L.push("");
|
||||
}
|
||||
if (adl) {
|
||||
if (priv) {
|
||||
L.push(" friend struct nlohmann::adl_serializer<" + T + ">;");
|
||||
}
|
||||
L.push("};");
|
||||
L.push("} // namespace " + ns);
|
||||
L.push("");
|
||||
L.push("namespace nlohmann");
|
||||
L.push("{");
|
||||
L.push("template <>");
|
||||
L.push("struct adl_serializer<" + ns + "::" + T + ">");
|
||||
L.push("{");
|
||||
L.push(" // note: the return type is the value, not void, and there is no default construction");
|
||||
L.push(" static " + ns + "::" + T + " from_json(const json& j)");
|
||||
L.push(" {");
|
||||
var args = st.members.map(function (mb) {
|
||||
var key = names ? cppString(mb.key) : cppString(mb.name);
|
||||
var ty = mb.mtype.trim();
|
||||
return st.missing === "default"
|
||||
? "j.value(" + key + ", " + ty + "{})"
|
||||
: "j.at(" + key + ").get<" + ty + ">()";
|
||||
});
|
||||
if (st.base === "yes") args.unshift("j.get<" + ns + "::" + B + ">()");
|
||||
L.push(" return " + ns + "::" + T + "{" + args.join(", ") + "};");
|
||||
L.push(" }");
|
||||
L.push("");
|
||||
L.push(" // you must provide to_json as well: the specialization replaces the default one");
|
||||
L.push(" static void to_json(json& j, const " + ns + "::" + T + "& t)");
|
||||
L.push(" {");
|
||||
if (st.base === "yes") L.push(" j = static_cast<const " + ns + "::" + B + "&>(t);");
|
||||
else L.push(" j = json::object();");
|
||||
st.members.forEach(function (mb) {
|
||||
L.push(" j[" + (names ? cppString(mb.key) : cppString(mb.name)) + "] = t." + mb.name + ";");
|
||||
});
|
||||
L.push(" }");
|
||||
L.push("};");
|
||||
L.push("} // namespace nlohmann");
|
||||
} else if (priv) {
|
||||
L.push(macroCall(res.macro, head, pairs, " "));
|
||||
L.push("};");
|
||||
} else {
|
||||
L.push("};");
|
||||
L.push("");
|
||||
L.push(macroCall(res.macro, head, pairs, ""));
|
||||
}
|
||||
if (!adl) L.push("} // namespace " + ns);
|
||||
L.push("");
|
||||
L.push("int main()");
|
||||
L.push("{");
|
||||
var Q = ns + "::" + T;
|
||||
if (noCtor) {
|
||||
var init = st.members.map(function (mb) { return mb.mtype.trim() + "{}"; });
|
||||
if (st.base === "yes") init.unshift(ns + "::" + B + "{}");
|
||||
L.push(" " + Q + " p{" + init.join(", ") + "};");
|
||||
} else {
|
||||
L.push(" " + Q + " p{};");
|
||||
}
|
||||
L.push(" json j = p; // to_json");
|
||||
L.push(" std::cout << j.dump() << '\\n';");
|
||||
if (st.dir === "both") {
|
||||
L.push("");
|
||||
L.push(" auto p2 = j.get<" + Q + ">(); // from_json");
|
||||
L.push(" std::cout << (json(p2) == j) << '\\n'; // round trip: 1");
|
||||
if (st.missing === "default") {
|
||||
L.push("");
|
||||
L.push(" // missing keys keep the defaults");
|
||||
L.push(" std::cout << json(json::object().get<" + Q + ">()).dump() << '\\n';");
|
||||
} else {
|
||||
L.push(" // a missing key throws json::out_of_range (403)");
|
||||
}
|
||||
}
|
||||
L.push("}");
|
||||
return L.join("\n") + "\n";
|
||||
}
|
||||
|
||||
function copyText(text, done) {
|
||||
function fallback() {
|
||||
var ta = el("textarea", { readonly: "", "aria-hidden": "true" });
|
||||
ta.value = text;
|
||||
ta.style.position = "fixed";
|
||||
ta.style.opacity = "0";
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
var ok = false;
|
||||
try { ok = document.execCommand("copy"); } catch (e) { ok = false; }
|
||||
document.body.removeChild(ta);
|
||||
done(ok);
|
||||
}
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(function () { done(true); }, fallback);
|
||||
} else {
|
||||
fallback();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- DOM ---- */
|
||||
function setup(root) {
|
||||
if (root.dataset.njMcReady) return;
|
||||
root.dataset.njMcReady = "1";
|
||||
var uid = "nj-mc-" + (++counter);
|
||||
var variant = root.getAttribute("data-variant") || "full";
|
||||
var preset = parsePreset(root.getAttribute("data-preset"));
|
||||
|
||||
var links = {};
|
||||
Array.prototype.forEach.call(root.querySelectorAll("a[data-nj-mc]"), function (a) {
|
||||
links[a.getAttribute("data-nj-mc")] = a.getAttribute("href");
|
||||
});
|
||||
|
||||
var st = { edit: "yes", access: "public", base: "no", dir: "both", missing: "throw", keys: "members", ctor: "yes",
|
||||
type: "person", baseName: "entity",
|
||||
members: [{ mtype: "std::string", name: "name", key: "name" }, { mtype: "int", name: "age", key: "age" }] };
|
||||
QUESTIONS.forEach(function (q) {
|
||||
if (preset[q.id] && q.options.some(function (o) { return o[0] === preset[q.id]; })) st[q.id] = preset[q.id];
|
||||
});
|
||||
|
||||
var fallback = root.querySelector(".nj-mc-fallback");
|
||||
if (fallback) fallback.hidden = true;
|
||||
|
||||
var app = el("div", { "class": "nj-mc-app" });
|
||||
var form = el("form", { "class": "nj-mc-form", "aria-label": "Macro chooser options" });
|
||||
form.addEventListener("submit", function (e) { e.preventDefault(); });
|
||||
|
||||
var qgrid = el("div", { "class": "nj-mc-questions" });
|
||||
var notes = {};
|
||||
var groups = {};
|
||||
QUESTIONS.forEach(function (q) {
|
||||
var fs = el("fieldset", { "class": "nj-mc-q" });
|
||||
fs.appendChild(el("legend", null, q.legend));
|
||||
var seg = el("div", { "class": "nj-mc-seg" });
|
||||
q.options.forEach(function (o) {
|
||||
var id = uid + "-" + q.id + "-" + o[0];
|
||||
var inp = el("input", { type: "radio", id: id, name: uid + "-" + q.id, value: o[0] });
|
||||
if (st[q.id] === o[0]) inp.checked = true;
|
||||
inp.addEventListener("change", function () { st[q.id] = o[0]; update(); });
|
||||
seg.appendChild(inp);
|
||||
seg.appendChild(el("label", { "for": id }, o[1]));
|
||||
});
|
||||
fs.appendChild(seg);
|
||||
var note = el("p", { "class": "nj-mc-qnote", id: uid + "-" + q.id + "-note" });
|
||||
note.hidden = true;
|
||||
fs.appendChild(note);
|
||||
fs.setAttribute("aria-describedby", note.id);
|
||||
notes[q.id] = note;
|
||||
groups[q.id] = fs;
|
||||
qgrid.appendChild(fs);
|
||||
});
|
||||
form.appendChild(qgrid);
|
||||
|
||||
/* type editor */
|
||||
var editor = el("div", { "class": "nj-mc-editor" });
|
||||
var names = el("div", { "class": "nj-mc-names" });
|
||||
function textField(label, value, onInput, cls) {
|
||||
var id = uid + "-f" + (++counter);
|
||||
var w = el("div", { "class": "nj-mc-field" + (cls ? " " + cls : "") });
|
||||
w.appendChild(el("label", { "for": id }, label));
|
||||
var inp = el("input", { type: "text", id: id, spellcheck: "false", autocomplete: "off", autocapitalize: "off" });
|
||||
inp.value = value;
|
||||
inp.addEventListener("input", function () { onInput(inp.value); update(); });
|
||||
w.appendChild(inp);
|
||||
return w;
|
||||
}
|
||||
names.appendChild(textField("Type name", st.type, function (v) { st.type = v.trim(); }));
|
||||
var baseField = textField("Base class", st.baseName, function (v) { st.baseName = v.trim(); }, "nj-mc-basefield");
|
||||
names.appendChild(baseField);
|
||||
editor.appendChild(names);
|
||||
|
||||
var mtable = el("div", { "class": "nj-mc-members", role: "group", "aria-label": "Member variables" });
|
||||
var mhead = el("div", { "class": "nj-mc-mrow nj-mc-mhead", "aria-hidden": "true" });
|
||||
mhead.appendChild(el("span", null, "Member type"));
|
||||
mhead.appendChild(el("span", null, "Member name"));
|
||||
var keyHead = el("span", { "class": "nj-mc-keycol" }, "JSON key");
|
||||
mhead.appendChild(keyHead);
|
||||
mhead.appendChild(el("span"));
|
||||
mtable.appendChild(mhead);
|
||||
var mbody = el("div", { "class": "nj-mc-mbody" });
|
||||
mtable.appendChild(mbody);
|
||||
var addBtn = el("button", { type: "button", "class": "nj-mc-add" }, "Add member");
|
||||
addBtn.addEventListener("click", function () {
|
||||
var k = st.members.length + 1;
|
||||
var nm = "member" + k;
|
||||
while (st.members.some(function (mb) { return mb.name === nm; })) nm = "member" + (++k);
|
||||
st.members.push({ mtype: "int", name: nm, key: nm });
|
||||
renderMembers();
|
||||
update();
|
||||
var rows = mbody.querySelectorAll(".nj-mc-mrow");
|
||||
var last = rows[rows.length - 1];
|
||||
if (last) last.querySelector("input").focus();
|
||||
});
|
||||
editor.appendChild(mtable);
|
||||
editor.appendChild(addBtn);
|
||||
if (variant === "full") form.appendChild(editor);
|
||||
|
||||
function renderMembers() {
|
||||
mbody.textContent = "";
|
||||
st.members.forEach(function (mb, i) {
|
||||
var row = el("div", { "class": "nj-mc-mrow" });
|
||||
function cell(prop, label, cls) {
|
||||
var inp = el("input", { type: "text", spellcheck: "false", autocomplete: "off", autocapitalize: "off",
|
||||
"aria-label": label + " of member " + (i + 1), placeholder: label, "class": cls || "" });
|
||||
inp.value = mb[prop];
|
||||
inp.addEventListener("input", function () {
|
||||
var keyFollows = prop === "name" && mb.key === mb.name;
|
||||
mb[prop] = prop === "key" ? inp.value : inp.value.trim();
|
||||
if (keyFollows) {
|
||||
mb.key = mb.name;
|
||||
var k = row.querySelector(".nj-mc-keycol");
|
||||
if (k) k.value = mb.key;
|
||||
}
|
||||
update();
|
||||
});
|
||||
row.appendChild(inp);
|
||||
}
|
||||
cell("mtype", "Type");
|
||||
cell("name", "Name");
|
||||
cell("key", "JSON key", "nj-mc-keycol");
|
||||
var rm = el("button", { type: "button", "class": "nj-mc-rm", "aria-label": "Remove member " + (i + 1), title: "Remove member" });
|
||||
rm.appendChild(el("span", { "aria-hidden": "true" }, "\u00d7"));
|
||||
rm.addEventListener("click", function () {
|
||||
st.members.splice(i, 1);
|
||||
renderMembers();
|
||||
update();
|
||||
var btns = mbody.querySelectorAll(".nj-mc-rm");
|
||||
(btns[Math.min(i, btns.length - 1)] || addBtn).focus();
|
||||
});
|
||||
row.appendChild(rm);
|
||||
mbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
renderMembers();
|
||||
app.appendChild(form);
|
||||
|
||||
/* output */
|
||||
var out = el("div", { "class": "nj-mc-out" });
|
||||
var result = el("div", { "class": "nj-mc-result", role: "status", "aria-live": "polite" });
|
||||
out.appendChild(result);
|
||||
var hints = el("ul", { "class": "nj-mc-hints" });
|
||||
out.appendChild(hints);
|
||||
var codeWrap = el("div", { "class": "nj-mc-code" });
|
||||
var bar = el("div", { "class": "nj-mc-codebar" });
|
||||
bar.appendChild(el("span", { "class": "nj-mc-codetitle" }, "Generated code (C++11)"));
|
||||
var copyBtn = el("button", { type: "button", "class": "nj-mc-copy", "aria-label": "Copy generated code" }, "Copy");
|
||||
var copyStatus = el("span", { "class": "nj-mc-copystatus", role: "status", "aria-live": "polite" });
|
||||
bar.appendChild(copyStatus);
|
||||
bar.appendChild(copyBtn);
|
||||
codeWrap.appendChild(bar);
|
||||
var hl = el("div", { "class": "language-cpp highlight" });
|
||||
var pre = el("pre");
|
||||
var code = el("code", { tabindex: "0", "aria-label": "Generated C++ code" });
|
||||
pre.appendChild(code);
|
||||
hl.appendChild(pre);
|
||||
codeWrap.appendChild(hl);
|
||||
if (variant === "full") out.appendChild(codeWrap);
|
||||
app.appendChild(out);
|
||||
root.appendChild(app);
|
||||
|
||||
var currentCode = "";
|
||||
var statusTimer = null;
|
||||
copyBtn.addEventListener("click", function () {
|
||||
copyText(currentCode, function (ok) {
|
||||
copyStatus.textContent = ok ? "Copied" : "Press Ctrl+C / Cmd+C to copy";
|
||||
if (!ok) {
|
||||
var r = document.createRange();
|
||||
r.selectNodeContents(code);
|
||||
var s = window.getSelection();
|
||||
s.removeAllRanges();
|
||||
s.addRange(r);
|
||||
}
|
||||
clearTimeout(statusTimer);
|
||||
statusTimer = setTimeout(function () { copyStatus.textContent = ""; }, 2500);
|
||||
});
|
||||
});
|
||||
|
||||
function setNote(id, text) {
|
||||
notes[id].hidden = !text;
|
||||
notes[id].textContent = text || "";
|
||||
}
|
||||
|
||||
function addLink(parent, key, text) {
|
||||
if (!key || !links[key]) return;
|
||||
parent.appendChild(document.createTextNode(" "));
|
||||
parent.appendChild(el("a", { href: links[key] }, text));
|
||||
}
|
||||
|
||||
function update() {
|
||||
var dirTo = st.dir === "to";
|
||||
groups.missing.disabled = dirTo;
|
||||
groups.missing.classList.toggle("nj-mc-off", dirTo);
|
||||
setNote("missing", dirTo ? "Not applicable: ONLY_SERIALIZE generates no from_json." : "");
|
||||
setNote("access", st.edit === "no" && st.access === "private" ? "Private members of a class you cannot edit are out of reach for every macro." : "");
|
||||
setNote("ctor", st.ctor === "no" && !dirTo ? "The macros default-construct the type when parsing: this gives an adl_serializer specialization instead." : "");
|
||||
baseField.hidden = st.base !== "yes";
|
||||
mtable.classList.toggle("nj-mc-custom", st.keys === "custom");
|
||||
keyHead.hidden = st.keys !== "custom";
|
||||
|
||||
var res = decide(st);
|
||||
result.textContent = "";
|
||||
hints.textContent = "";
|
||||
if ((res.macro || res.adl) && !res.errors.length) {
|
||||
result.className = "nj-mc-result";
|
||||
result.appendChild(el("p", { "class": "nj-mc-label" }, res.adl ? "No macro fits, write a serializer" : "Recommended macro"));
|
||||
var name = el("p", { "class": "nj-mc-macro" });
|
||||
name.appendChild(el("code", null, res.adl ? "adl_serializer<" + st.type + ">" : res.macro));
|
||||
result.appendChild(name);
|
||||
var why = el("p", { "class": "nj-mc-why" }, res.reason + " " + res.place);
|
||||
addLink(why, res.ref, res.adl ? "Details" : "Reference");
|
||||
result.appendChild(why);
|
||||
} else {
|
||||
result.className = "nj-mc-result nj-mc-bad";
|
||||
result.appendChild(el("p", { "class": "nj-mc-label" }, (res.macro || res.adl) ? "Cannot generate this code yet" : "No macro fits"));
|
||||
res.errors.forEach(function (e) {
|
||||
var p = el("p", { "class": "nj-mc-why" }, e.text);
|
||||
addLink(p, e.link, e.linkText);
|
||||
result.appendChild(p);
|
||||
});
|
||||
}
|
||||
res.warnings.forEach(function (w) { hints.appendChild(el("li", null, w)); });
|
||||
hints.hidden = !res.warnings.length;
|
||||
|
||||
var ok = (res.macro || res.adl) && !res.errors.length;
|
||||
codeWrap.hidden = !ok;
|
||||
if (ok) {
|
||||
currentCode = generate(st, res);
|
||||
code.innerHTML = highlight(currentCode);
|
||||
}
|
||||
root.setAttribute("data-nj-mc-macro", ok ? (res.macro || "adl_serializer") : "");
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
function init() {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".nj-macro-chooser"), setup);
|
||||
}
|
||||
|
||||
if (typeof document$ !== "undefined" && document$ && typeof document$.subscribe === "function") {
|
||||
document$.subscribe(init);
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
/* exported for the verification scripts */
|
||||
window.njMacroChooser = { decide: decide, generate: generate };
|
||||
})();
|
||||
@@ -0,0 +1,306 @@
|
||||
/* figures: pointer
|
||||
*
|
||||
* Two widgets that enhance static markup (the markup alone is a complete, readable figure):
|
||||
* - [data-nj-pointer-walk] in features/json_pointer.md: a tree of the example document plus a pointer bar whose
|
||||
* segments select a prefix of the pointer; the path down to that level is highlighted and its value is shown.
|
||||
* - [data-nj-patch] in features/json_patch.md: a stepper through six JSON Patch operations; the static table of the
|
||||
* operations is hidden once the stepper is built.
|
||||
* Initialised through Material's document$ (navigation.instant), idempotent per element.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function el(tag, cls, html) {
|
||||
var e = document.createElement(tag);
|
||||
if (cls) e.className = cls;
|
||||
if (html !== undefined) e.innerHTML = html;
|
||||
return e;
|
||||
}
|
||||
function esc(s) {
|
||||
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
|
||||
/* ---------- JSON Pointer walker ---------- */
|
||||
|
||||
var PRESETS = ["", "/array", "/array/0", "/array/1", "/array/2", "/nested", "/nested/one", "/nested/two",
|
||||
"/nested/three", "/nested/three/0", "/nested/three/1", "/"];
|
||||
|
||||
function tokens(ptr) {
|
||||
if (ptr === "") return [];
|
||||
return ptr.slice(1).split("/").map(function (t) { return t.replace(/~1/g, "/").replace(/~0/g, "~"); });
|
||||
}
|
||||
function joinTokens(toks) {
|
||||
return toks.map(function (t) { return "/" + t.replace(/~/g, "~0").replace(/\//g, "~1"); }).join("");
|
||||
}
|
||||
// RFC 6901 resolution as json::at() does it; returns {ok, value, depth, error}
|
||||
function resolve(doc, toks) {
|
||||
var v = doc;
|
||||
for (var i = 0; i < toks.length; ++i) {
|
||||
var t = toks[i];
|
||||
if (Array.isArray(v)) {
|
||||
if (!/^(0|[1-9][0-9]*)$/.test(t) || Number(t) >= v.length) {
|
||||
return { ok: false, depth: i, error: "at() throws out_of_range: array index '" + t + "' is out of range" };
|
||||
}
|
||||
v = v[Number(t)];
|
||||
} else if (v !== null && typeof v === "object") {
|
||||
if (!Object.prototype.hasOwnProperty.call(v, t)) {
|
||||
return { ok: false, depth: i, error: "at() throws out_of_range.403: key '" + t + "' not found" };
|
||||
}
|
||||
v = v[t];
|
||||
} else {
|
||||
return { ok: false, depth: i, error: "a primitive value has no children" };
|
||||
}
|
||||
}
|
||||
return { ok: true, value: v, depth: toks.length };
|
||||
}
|
||||
function ptrLabel(p) { return p === "" ? '""' : p; }
|
||||
|
||||
function initWalk(root) {
|
||||
if (root.dataset.njReady) return;
|
||||
var doc;
|
||||
try { doc = JSON.parse(root.getAttribute("data-doc")); } catch (e) { return; }
|
||||
root.dataset.njReady = "1";
|
||||
root.classList.add("nj-pointer-walk--js");
|
||||
|
||||
var tree = root.querySelector(".nj-pointer-tree");
|
||||
var items = Array.prototype.slice.call(tree.querySelectorAll("li[data-p]"));
|
||||
var state = { ptr: "/nested/three/1", level: 3 };
|
||||
|
||||
// tree labels become buttons that select their own pointer
|
||||
items.forEach(function (li) {
|
||||
var span = li.querySelector(":scope > .nj-pointer-n");
|
||||
var b = el("button", "nj-pointer-n", span.innerHTML);
|
||||
b.type = "button";
|
||||
var p = li.getAttribute("data-p");
|
||||
b.setAttribute("aria-label", "Select the pointer " + ptrLabel(p));
|
||||
b.addEventListener("click", function () { set(p, tokens(p).length); });
|
||||
span.replaceWith(b);
|
||||
});
|
||||
|
||||
var panel = el("div", "nj-pointer-panel");
|
||||
panel.appendChild(el("div", "nj-pointer-lbl", "Pointer <span>(select a segment)</span>"));
|
||||
var bar = el("div", "nj-pointer-bar");
|
||||
bar.setAttribute("role", "group");
|
||||
bar.setAttribute("aria-label", "Pointer segments");
|
||||
panel.appendChild(bar);
|
||||
var out = el("div", "nj-pointer-out");
|
||||
out.setAttribute("aria-live", "polite");
|
||||
panel.appendChild(out);
|
||||
panel.appendChild(el("div", "nj-pointer-lbl", "Examples"));
|
||||
var presets = el("div", "nj-pointer-presets");
|
||||
presets.setAttribute("role", "group");
|
||||
presets.setAttribute("aria-label", "Example pointers");
|
||||
PRESETS.forEach(function (p) {
|
||||
var b = el("button", "nj-pointer-chip", "<code>" + esc(ptrLabel(p)) + "</code>");
|
||||
b.type = "button";
|
||||
b.setAttribute("data-p", p);
|
||||
b.setAttribute("aria-label", p === "" ? "Pointer \"\" (the whole document)" : "Pointer " + p);
|
||||
b.addEventListener("click", function () { set(p, tokens(p).length); });
|
||||
presets.appendChild(b);
|
||||
});
|
||||
panel.appendChild(presets);
|
||||
var stat = root.querySelector(".nj-pointer-static");
|
||||
if (stat) stat.hidden = true;
|
||||
root.insertBefore(panel, root.firstChild);
|
||||
|
||||
function liFor(p) {
|
||||
for (var i = 0; i < items.length; ++i) if (items[i].getAttribute("data-p") === p) return items[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
function render() {
|
||||
var toks = tokens(state.ptr);
|
||||
var sel = toks.slice(0, state.level);
|
||||
var r = resolve(doc, sel);
|
||||
|
||||
// bar: root chip plus one button per reference token
|
||||
bar.innerHTML = "";
|
||||
var parts = [{ label: '""', text: "root", n: 0 }];
|
||||
toks.forEach(function (t, i) { parts.push({ label: joinTokens([t]), n: i + 1 }); });
|
||||
parts.forEach(function (part) {
|
||||
var b = el("button", "nj-pointer-seg" + (part.n === 0 ? " nj-pointer-seg--root" : ""),
|
||||
part.n === 0 ? "<code>\"\"</code>" : "<code>" + esc(part.label) + "</code>");
|
||||
b.type = "button";
|
||||
b.setAttribute("aria-pressed", part.n === state.level ? "true" : "false");
|
||||
if (part.n > state.level) b.classList.add("is-after");
|
||||
var prefix = joinTokens(toks.slice(0, part.n));
|
||||
b.setAttribute("aria-label", "Go to level " + part.n + ": " + ptrLabel(prefix) + (part.n === 0 ? " (the whole document)" : ""));
|
||||
b.addEventListener("click", function () { state.level = part.n; render(); focusSeg(part.n); });
|
||||
bar.appendChild(b);
|
||||
});
|
||||
|
||||
// tree highlight
|
||||
items.forEach(function (li) { li.classList.remove("is-on", "is-target", "is-pass", "is-miss"); });
|
||||
var depth = r.ok ? sel.length : r.depth;
|
||||
for (var d = 0; d <= depth; ++d) {
|
||||
var li = liFor(joinTokens(sel.slice(0, d)));
|
||||
if (!li) break;
|
||||
li.classList.add("is-on");
|
||||
if (d > 0) {
|
||||
for (var s = li.previousElementSibling; s; s = s.previousElementSibling) s.classList.add("is-pass");
|
||||
}
|
||||
if (r.ok && d === depth) li.classList.add("is-target");
|
||||
}
|
||||
if (!r.ok) {
|
||||
var miss = liFor(joinTokens(sel.slice(0, r.depth)));
|
||||
if (miss) miss.classList.add("is-miss");
|
||||
}
|
||||
|
||||
var shown = joinTokens(sel);
|
||||
out.innerHTML = r.ok
|
||||
? "<code>" + esc(ptrLabel(shown)) + "</code> resolves to <code class=\"nj-pointer-val\">" + esc(JSON.stringify(r.value)) + "</code>" +
|
||||
(shown === "" ? " <span class=\"nj-pointer-note\">(the whole document)</span>" : "")
|
||||
: "<code>" + esc(ptrLabel(shown)) + "</code> <span class=\"nj-pointer-err\">identifies no value</span>: " + esc(r.error) +
|
||||
". <span class=\"nj-pointer-note\">The token after <code>/</code> is the empty key, and the document has none.</span>";
|
||||
|
||||
Array.prototype.forEach.call(presets.children, function (b) {
|
||||
b.setAttribute("aria-pressed", b.getAttribute("data-p") === shown && state.level === tokens(state.ptr).length ? "true" : "false");
|
||||
});
|
||||
}
|
||||
function focusSeg(n) {
|
||||
var b = bar.children[n];
|
||||
if (b) b.focus();
|
||||
}
|
||||
function set(p, level) {
|
||||
state.ptr = p;
|
||||
state.level = level;
|
||||
render();
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
/* ---------- JSON Patch stepper ---------- */
|
||||
|
||||
function k(s) { return '<span class="nj-patch-k">"' + s + '"</span>'; }
|
||||
function s(v) { return '<span class="nj-patch-s">"' + v + '"</span>'; }
|
||||
function ins(x) { return '<span class="nj-patch-ins">' + x + "</span>"; }
|
||||
function src(x) { return '<span class="nj-patch-src">' + x + "</span>"; }
|
||||
var I = " ";
|
||||
|
||||
var OPS = [
|
||||
{ op: "replace", path: "/baz", extra: ', "value": "boo"' },
|
||||
{ op: "add", path: "/hello", extra: ', "value": ["world"]' },
|
||||
{ op: "remove", path: "/foo", extra: "" },
|
||||
{ op: "move", from: "/baz", path: "/hello/1", extra: "" },
|
||||
{ op: "copy", from: "/hello/0", path: "/greeting", extra: "" },
|
||||
{ op: "test", path: "/greeting", extra: ', "value": "world"' }
|
||||
];
|
||||
// each frame: document lines [html, line class, note], optional connector {from, to}, status text
|
||||
var FRAMES = [
|
||||
{ lines: [["{"], [I + k("baz") + ": " + s("qux") + ","], [I + k("foo") + ": " + s("bar")], ["}"]],
|
||||
status: "Start: the original document of the example below. Press Next to apply the first operation." },
|
||||
{ lines: [["{"], [I + k("baz") + ": " + s("qux") + ",", "del"], [I + k("baz") + ": " + s("boo") + ",", "add", "replaced"],
|
||||
[I + k("foo") + ": " + s("bar")], ["}"]],
|
||||
status: "replace /baz: the value \"qux\" is replaced by \"boo\"." },
|
||||
{ lines: [["{"], [I + k("baz") + ": " + s("boo") + ","], [I + k("foo") + ": " + s("bar") + ","],
|
||||
[I + k("hello") + ": [" + s("world") + "]", "add", "added"], ["}"]],
|
||||
status: "add /hello: the new member \"hello\" with the value [\"world\"] is added." },
|
||||
{ lines: [["{"], [I + k("baz") + ": " + s("boo") + ","], [I + k("foo") + ": " + s("bar") + ",", "del", "removed"],
|
||||
[I + k("hello") + ": [" + s("world") + "]"], ["}"]],
|
||||
status: "remove /foo: the member \"foo\" is removed. This is the result of the example below." },
|
||||
{ lines: [["{"], [I + k("baz") + ": " + s("boo") + ",", "del", "moved"],
|
||||
[I + k("hello") + ": [" + s("world") + ", " + ins(s("boo")) + "]", "mod", "from /baz"], ["}"]],
|
||||
arc: { from: 1, to: 2 },
|
||||
status: "move from /baz to /hello/1: \"boo\" is removed from /baz and inserted into the array at index 1." },
|
||||
{ lines: [["{"], [I + k("greeting") + ": " + s("world") + ",", "add", "copy of /hello/0"],
|
||||
[I + k("hello") + ": [" + src(s("world")) + ", " + s("boo") + "]"], ["}"]],
|
||||
arc: { from: 2, to: 1 },
|
||||
status: "copy from /hello/0 to /greeting: the value \"world\" is copied to the new member \"greeting\"." },
|
||||
{ lines: [["{"], [I + k("greeting") + ": " + s("world") + ",", "test", "test passed"],
|
||||
[I + k("hello") + ": [" + s("world") + ", " + s("boo") + "]"], ["}"]],
|
||||
status: "test /greeting: the value equals \"world\", so the document is unchanged. A failed test would make patch() throw other_error 501." }
|
||||
];
|
||||
|
||||
function opHtml(o) {
|
||||
var h = '{"op": "<span class="nj-patch-op">' + o.op + '</span>"';
|
||||
if (o.from) h += ', "from": "<span class="nj-patch-path">' + o.from + '</span>"';
|
||||
h += ', "path": "<span class="nj-patch-path">' + o.path + '</span>"' + esc(o.extra) + "}";
|
||||
return h;
|
||||
}
|
||||
|
||||
function initPatch(root) {
|
||||
if (root.dataset.njReady) return;
|
||||
root.dataset.njReady = "1";
|
||||
root.classList.add("nj-patch--js");
|
||||
var step = 0;
|
||||
|
||||
var stage = el("div", "nj-patch-stage");
|
||||
var cols = el("div", "nj-patch-cols");
|
||||
var left = el("div", "nj-patch-col");
|
||||
left.appendChild(el("div", "nj-patch-h", "Document"));
|
||||
var docBox = el("div", "nj-patch-doc");
|
||||
left.appendChild(docBox);
|
||||
var right = el("div", "nj-patch-col");
|
||||
right.appendChild(el("div", "nj-patch-h", "Patch"));
|
||||
var list = el("ol", "nj-patch-ops");
|
||||
OPS.forEach(function (o) { list.appendChild(el("li", "", "<code>" + opHtml(o) + "</code>")); });
|
||||
right.appendChild(list);
|
||||
cols.appendChild(left);
|
||||
cols.appendChild(right);
|
||||
stage.appendChild(cols);
|
||||
// controls and status sit under the document, so on a phone both stay in view together
|
||||
|
||||
var ctrl = el("div", "nj-patch-ctrl");
|
||||
function btn(label, aria, fn) {
|
||||
var b = el("button", "nj-patch-btn", label);
|
||||
b.type = "button";
|
||||
b.setAttribute("aria-label", aria);
|
||||
b.addEventListener("click", fn);
|
||||
ctrl.appendChild(b);
|
||||
return b;
|
||||
}
|
||||
var bReset = btn("Reset", "Back to the original document", function () { step = 0; render(true); });
|
||||
var bPrev = btn("Back", "Previous operation", function () { if (step > 0) { step--; render(true); } });
|
||||
var bNext = btn("Next", "Apply the next operation", function () { if (step < OPS.length) { step++; render(true); } });
|
||||
bNext.classList.add("nj-patch-btn--primary");
|
||||
var count = el("span", "nj-patch-count");
|
||||
ctrl.appendChild(count);
|
||||
left.appendChild(ctrl);
|
||||
var status = el("p", "nj-patch-status");
|
||||
status.setAttribute("aria-live", "polite");
|
||||
left.appendChild(status);
|
||||
root.insertBefore(stage, root.firstChild);
|
||||
|
||||
function render(animate) {
|
||||
var f = FRAMES[step];
|
||||
docBox.innerHTML = "";
|
||||
f.lines.forEach(function (ln) {
|
||||
var row = el("div", "nj-patch-line" + (ln[1] ? " nj-patch-line--" + ln[1] : ""));
|
||||
row.appendChild(el("span", "nj-patch-code", ln[0]));
|
||||
if (ln[2]) row.appendChild(el("span", "nj-patch-note", esc(ln[2])));
|
||||
docBox.appendChild(row);
|
||||
});
|
||||
if (f.arc) {
|
||||
var a = el("div", "nj-patch-arc " + (f.arc.to > f.arc.from ? "nj-patch-arc--down" : "nj-patch-arc--up"));
|
||||
var top = Math.min(f.arc.from, f.arc.to), n = Math.abs(f.arc.to - f.arc.from);
|
||||
a.style.top = "calc(var(--nj-patch-pad) + " + (top + 0.5) + " * var(--nj-patch-lh))";
|
||||
a.style.height = "calc(" + n + " * var(--nj-patch-lh))";
|
||||
a.setAttribute("aria-hidden", "true");
|
||||
docBox.appendChild(a);
|
||||
}
|
||||
docBox.classList.toggle("nj-patch-doc--anim", !!animate);
|
||||
Array.prototype.forEach.call(list.children, function (li, i) {
|
||||
li.className = i + 1 === step ? "is-cur" : (i + 1 < step ? "is-done" : "");
|
||||
if (i + 1 === step) li.setAttribute("aria-current", "step"); else li.removeAttribute("aria-current");
|
||||
});
|
||||
bPrev.disabled = step === 0;
|
||||
bReset.disabled = step === 0;
|
||||
bNext.disabled = step === OPS.length;
|
||||
count.textContent = "Step " + step + " of " + OPS.length;
|
||||
status.textContent = f.status;
|
||||
}
|
||||
render(false);
|
||||
}
|
||||
|
||||
function init() {
|
||||
Array.prototype.forEach.call(document.querySelectorAll("[data-nj-pointer-walk]"), initWalk);
|
||||
Array.prototype.forEach.call(document.querySelectorAll("[data-nj-patch]"), initPatch);
|
||||
}
|
||||
if (typeof window.document$ !== "undefined" && window.document$.subscribe) {
|
||||
window.document$.subscribe(init);
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* figures: sax
|
||||
*
|
||||
* SAX event stepper on features/parsing/sax_interface.md. The page contains the final state as static HTML (input,
|
||||
* all nine events, the finished tree); this script adds the controls and redraws the three lanes for each step:
|
||||
* the consumed part of the input, the events called so far, and the state of one of two handlers (a DOM builder
|
||||
* that keeps a stack of open containers, or a handler that keeps only the value at /b). "return false at this
|
||||
* event" stops the parse at the current event: the later events are not called and the rest of the input is not read.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var SRC = '{"a":[1,true],"b":"x"}';
|
||||
// tok: [start, end) of the token that makes the parser call the event
|
||||
var EVENTS = [
|
||||
{ call: "start_object(-1)", cls: "os", tok: [0, 1], op: "so" },
|
||||
{ call: 'key("a")', cls: "k", tok: [1, 4], op: "key", v: "a" },
|
||||
{ call: "start_array(-1)", cls: "as", tok: [5, 6], op: "sa" },
|
||||
{ call: "number_unsigned(1)", cls: "v", tok: [6, 7], op: "val", v: { t: "n", v: "1" } },
|
||||
{ call: "boolean(true)", cls: "v", tok: [8, 12], op: "val", v: { t: "b", v: "true" } },
|
||||
{ call: "end_array()", cls: "ae", tok: [12, 13], op: "end" },
|
||||
{ call: 'key("b")', cls: "k", tok: [14, 17], op: "key", v: "b" },
|
||||
{ call: 'string("x")', cls: "v", tok: [18, 21], op: "val", v: { t: "s", v: '"x"' } },
|
||||
{ call: "end_object()", cls: "oe", tok: [21, 22], op: "end" }
|
||||
];
|
||||
var N = EVENTS.length;
|
||||
|
||||
function esc(s) {
|
||||
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
|
||||
// ---- handler A: DOM builder (like json_sax_dom_parser) ----
|
||||
function domState(steps) {
|
||||
var root = null, stack = [], last = null;
|
||||
function place(v) {
|
||||
if (!root) { root = v; return; }
|
||||
var top = stack[stack.length - 1];
|
||||
if (top.t === "a") { top.e.push(v); } else { top.m[top.m.length - 1][1] = v; }
|
||||
}
|
||||
for (var i = 0; i < steps; ++i) {
|
||||
var ev = EVENTS[i], v;
|
||||
if (ev.op === "so" || ev.op === "sa") {
|
||||
v = ev.op === "so" ? { t: "o", m: [] } : { t: "a", e: [] };
|
||||
var top = stack[stack.length - 1];
|
||||
v.ptr = !top ? "" : top.ptr + "/" + (top.t === "a" ? top.e.length : top.m[top.m.length - 1][0]);
|
||||
place(v); stack.push(v); last = v;
|
||||
} else if (ev.op === "key") {
|
||||
v = { t: "z", v: "null" }; // the DOM parser adds null at the key and fills it in later
|
||||
stack[stack.length - 1].m.push([ev.v, v]); last = v;
|
||||
} else if (ev.op === "val") {
|
||||
v = { t: ev.v.t, v: ev.v.v }; place(v); last = v;
|
||||
} else {
|
||||
last = stack.pop();
|
||||
}
|
||||
}
|
||||
return { root: root, stack: stack, last: last };
|
||||
}
|
||||
|
||||
function pretty(v, ind, last, out, prefix, suffix) {
|
||||
var pad = new Array(ind + 1).join(" ");
|
||||
var mark = function (s) { return v === last ? '<span class="nj-sax-hit">' + s + "</span>" : s; };
|
||||
if (v.t === "o" || v.t === "a") {
|
||||
var items = v.t === "o" ? v.m : v.e, open = v.t === "o" ? "{" : "[", close = v.t === "o" ? "}" : "]";
|
||||
if (!items.length) { out.push(pad + prefix + mark(open + close) + suffix); return; }
|
||||
out.push(pad + prefix + mark(open));
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var comma = i < items.length - 1 ? "," : "";
|
||||
if (v.t === "o") {
|
||||
pretty(items[i][1], ind + 1, last, out, esc('"' + items[i][0] + '": '), comma);
|
||||
} else {
|
||||
pretty(items[i], ind + 1, last, out, "", comma);
|
||||
}
|
||||
}
|
||||
out.push(pad + mark(close) + suffix);
|
||||
} else {
|
||||
out.push(pad + prefix + mark(esc(v.v)) + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
function renderDom(steps, stopped) {
|
||||
var st = domState(steps), lines = [];
|
||||
var tree = st.root ? (pretty(st.root, 0, st.last, lines, "", ""), lines.join("\n")) : '<span class="nj-sax-dim">(nothing built yet)</span>';
|
||||
var chips = st.stack.length ? st.stack.map(function (c) {
|
||||
return '<li><code>' + (c.t === "o" ? "{}" : "[]") + " " + esc(c.ptr === "" ? "root" : c.ptr) + "</code></li>";
|
||||
}).join("") : '<li class="nj-sax-dim">empty</li>';
|
||||
var note;
|
||||
if (steps === 0) { note = "The handler starts with an empty stack."; }
|
||||
else if (stopped) { note = "The handler keeps what it built so far; the containers on the stack stay unfinished."; }
|
||||
else if (steps === N) { note = "The tree is complete and no container is open. The number of elements is passed as -1: the parser does not know it in advance."; }
|
||||
else { note = EVENTS[steps - 1].op === "key" ? "A key adds the member with a null value, which the next value replaces." : "Values go into the container on top of the stack."; }
|
||||
return '<pre class="nj-sax__tree"><code>' + tree + "</code></pre>" +
|
||||
'<p class="nj-sax__stackh">stack of open containers</p><ol class="nj-sax__stack" reversed>' + chips + "</ol>" +
|
||||
'<p class="nj-sax__note">' + note + "</p>";
|
||||
}
|
||||
|
||||
// ---- handler B: keep only the value at /b ----
|
||||
function renderKeep(steps, stopped) {
|
||||
var frames = [], ptr = "", kept = null, keptAt = 0, action = "";
|
||||
var where = function () {
|
||||
return frames.map(function (f) { return "/" + (f.t === "a" ? f.i : f.k); }).join("");
|
||||
};
|
||||
for (var i = 0; i < steps; ++i) {
|
||||
var ev = EVENTS[i], top = frames[frames.length - 1];
|
||||
if (ev.op === "key") { top.k = ev.v; ptr = where(); action = "remembers the key"; continue; }
|
||||
if (ev.op === "end") { ptr = frames.pop().loc; action = "leaves the container"; continue; }
|
||||
ptr = where();
|
||||
action = ptr === "/b" ? "keeps it" : "ignores it";
|
||||
if (top && top.t === "a") { top.i++; }
|
||||
if (ev.op === "so") { frames.push({ t: "o", k: "", loc: ptr }); }
|
||||
else if (ev.op === "sa") { frames.push({ t: "a", i: 0, loc: ptr }); }
|
||||
else if (ptr === "/b") { kept = ev.v.v; keptAt = i + 1; }
|
||||
}
|
||||
var note;
|
||||
if (steps === 0) { note = "The handler keeps only the current position and, once it is found, one value."; }
|
||||
else if (stopped) { note = kept !== null ? "The value is found, so stopping here saves reading the rest of the input." : "Stopped before /b was read: nothing is kept."; }
|
||||
else { note = "At " + (ptr === "" ? "the root" : ptr) + " the handler " + action + "."; }
|
||||
if (!stopped && steps === keptAt && kept !== null) {
|
||||
note += " A handler that needs nothing else could return false here.";
|
||||
}
|
||||
return '<dl class="nj-sax__kv">' +
|
||||
"<dt>position</dt><dd><code>" + (steps ? esc(ptr === "" ? "(root)" : ptr) : "-") + "</code></dd>" +
|
||||
"<dt>kept value</dt><dd>" + (kept !== null ? '<code class="' + (steps === keptAt ? "nj-sax-hit" : "") + '">' + esc(kept) + "</code>" : '<span class="nj-sax-dim">none</span>') + "</dd>" +
|
||||
"</dl>" +
|
||||
'<p class="nj-sax__note">' + note + " No tree is built.</p>";
|
||||
}
|
||||
|
||||
function init(root) {
|
||||
if (root.getAttribute("data-nj-sax-ready")) { return; }
|
||||
root.setAttribute("data-nj-sax-ready", "1");
|
||||
var src = root.querySelector(".nj-sax__src");
|
||||
var items = root.querySelectorAll(".nj-sax__events li");
|
||||
var head = root.querySelector(".nj-sax__lane--state .nj-sax__h");
|
||||
var state = root.querySelector(".nj-sax__state");
|
||||
var reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
var step = 0, mode = "dom", stopAt = 0, timer = null;
|
||||
|
||||
// controls
|
||||
var bar = document.createElement("div");
|
||||
bar.className = "nj-sax__controls";
|
||||
bar.innerHTML =
|
||||
'<div class="nj-sax__btns">' +
|
||||
'<button type="button" class="nj-sax-btn" data-a="reset" aria-label="Reset to before the first event">Reset</button>' +
|
||||
'<button type="button" class="nj-sax-btn" data-a="prev" aria-label="Previous event">Prev</button>' +
|
||||
'<button type="button" class="nj-sax-btn nj-sax-btn--main" data-a="play" aria-label="Play all events">Play</button>' +
|
||||
'<button type="button" class="nj-sax-btn" data-a="next" aria-label="Next event">Next</button>' +
|
||||
"</div>" +
|
||||
'<label class="nj-sax__stop"><input type="checkbox" data-a="stop"> return <code>false</code> at this event</label>' +
|
||||
'<div class="nj-sax__modes" role="group" aria-label="handler">' +
|
||||
'<button type="button" class="nj-sax-btn" data-m="dom" aria-pressed="true">build a tree</button>' +
|
||||
'<button type="button" class="nj-sax-btn" data-m="keep" aria-pressed="false">keep only /b</button>' +
|
||||
"</div>";
|
||||
root.insertBefore(bar, root.firstChild);
|
||||
var status = document.createElement("p");
|
||||
status.className = "nj-sax__status";
|
||||
status.setAttribute("aria-live", "polite");
|
||||
root.appendChild(status);
|
||||
var btn = function (a) { return bar.querySelector('[data-a="' + a + '"]'); };
|
||||
var stopBox = btn("stop");
|
||||
|
||||
function stopped() { return stopAt > 0 && step === stopAt; }
|
||||
|
||||
function render() {
|
||||
var end = step ? EVENTS[step - 1].tok[1] : 0, cur = step ? EVENTS[step - 1] : null;
|
||||
var html = "";
|
||||
if (cur) {
|
||||
html += '<span class="nj-sax-read">' + esc(SRC.slice(0, cur.tok[0])) + "</span>";
|
||||
html += '<span class="nj-sax-read nj-sax-tok nj-sax-tok--' + cur.cls + '">' + esc(SRC.slice(cur.tok[0], end)) + "</span>";
|
||||
}
|
||||
html += '<span class="' + (stopped() ? "nj-sax-unread nj-sax-unread--never" : "nj-sax-unread") + '">' + esc(SRC.slice(end)) + "</span>";
|
||||
src.innerHTML = html;
|
||||
for (var i = 0; i < N; ++i) {
|
||||
var li = items[i];
|
||||
li.classList.toggle("nj-sax-ev--done", i < step);
|
||||
li.classList.toggle("nj-sax-ev--cur", i === step - 1);
|
||||
li.classList.toggle("nj-sax-ev--todo", i >= step && !stopped());
|
||||
li.classList.toggle("nj-sax-ev--skip", i >= step && stopped());
|
||||
li.classList.toggle("nj-sax-ev--false", stopped() && i === step - 1);
|
||||
if (i === step - 1) { li.setAttribute("aria-current", "step"); } else { li.removeAttribute("aria-current"); }
|
||||
}
|
||||
head.innerHTML = mode === "dom" ? "handler: build a value tree, as <code>parse</code> does" : "handler: keep only the value at <code>/b</code>";
|
||||
state.innerHTML = mode === "dom" ? renderDom(step, stopped()) : renderKeep(step, stopped());
|
||||
bar.querySelectorAll("[data-m]").forEach(function (b) { b.setAttribute("aria-pressed", String(b.getAttribute("data-m") === mode)); });
|
||||
btn("prev").disabled = step === 0;
|
||||
btn("reset").disabled = step === 0;
|
||||
btn("next").disabled = step === N || stopped();
|
||||
stopBox.disabled = step === 0;
|
||||
stopBox.checked = stopped();
|
||||
var playing = timer !== null;
|
||||
btn("play").textContent = playing ? "Pause" : "Play";
|
||||
btn("play").setAttribute("aria-label", playing ? "Pause" : "Play all events");
|
||||
var msg;
|
||||
if (step === 0) { msg = "Before the first event: nothing is read yet."; }
|
||||
else if (stopped()) { msg = "Event " + step + " of " + N + ": " + cur.call + " returned false. Parsing stops, sax_parse returns false, and the rest of the input is not read."; }
|
||||
else if (step === N) { msg = "Event " + N + " of " + N + ": " + cur.call + ". The input is read completely and sax_parse returns true."; }
|
||||
else { msg = "Event " + step + " of " + N + ": the parser read " + SRC.slice(cur.tok[0], cur.tok[1]) + " and called " + cur.call + "."; }
|
||||
status.textContent = msg;
|
||||
}
|
||||
|
||||
function stop() { if (timer !== null) { clearInterval(timer); timer = null; } }
|
||||
function go(s) {
|
||||
step = Math.max(0, Math.min(N, s));
|
||||
if (stopAt && step < stopAt) { stopAt = 0; }
|
||||
render();
|
||||
}
|
||||
|
||||
bar.addEventListener("click", function (e) {
|
||||
var b = e.target.closest("button");
|
||||
if (!b) { return; }
|
||||
var m = b.getAttribute("data-m");
|
||||
if (m) { mode = m; render(); return; }
|
||||
var a = b.getAttribute("data-a");
|
||||
if (a === "play") {
|
||||
if (timer !== null) { stop(); render(); return; }
|
||||
if (step === N || stopped()) { stopAt = 0; step = 0; }
|
||||
timer = setInterval(function () {
|
||||
if (step >= N || stopped()) { stop(); render(); return; }
|
||||
go(step + 1);
|
||||
if (step >= N) { stop(); render(); }
|
||||
}, reduce ? 1400 : 900);
|
||||
go(step + 1);
|
||||
return;
|
||||
}
|
||||
stop();
|
||||
if (a === "reset") { stopAt = 0; go(0); }
|
||||
else if (a === "prev") { go(step - 1); }
|
||||
else if (a === "next") { go(step + 1); }
|
||||
});
|
||||
stopBox.addEventListener("change", function () {
|
||||
stop();
|
||||
stopAt = stopBox.checked ? step : 0;
|
||||
render();
|
||||
});
|
||||
root.classList.add("nj-sax--live");
|
||||
render();
|
||||
}
|
||||
|
||||
function setup() {
|
||||
document.querySelectorAll("[data-nj-sax]").forEach(init);
|
||||
}
|
||||
|
||||
if (typeof window.document$ !== "undefined" && window.document$ && typeof window.document$.subscribe === "function") {
|
||||
window.document$.subscribe(setup);
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", setup);
|
||||
} else {
|
||||
setup();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Brand look for Mermaid diagrams.
|
||||
*
|
||||
* Material for MkDocs loads Mermaid on demand and calls mermaid.initialize() with its own theme CSS. This script
|
||||
* catches Mermaid when it arrives on window.mermaid and wraps two calls:
|
||||
* - initialize(): adds the brand's font, spacing and shape rules below to Material's options;
|
||||
* - render(): marks hexagons ({{...}} in a flowchart) with the class "nj-decision", since in the SVG a hexagon and
|
||||
* a parallelogram are both a <polygon> that CSS cannot tell apart; and keeps a wide diagram at 80 % of its size
|
||||
* or more, so its text stays readable and the diagram scrolls sideways instead (see .mermaid in brand.css).
|
||||
* The colours come from the --md-mermaid-* variables in brand.css, so the diagrams follow the light/dark toggle.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var css = [
|
||||
// text: Inter everywhere, labels on boxes semibold
|
||||
".label, .nodeLabel, .nodeLabel p, .edgeLabel, .edgeLabel p, .cluster span, text { font-family: var(--md-mermaid-font-family) !important; }",
|
||||
".nodeLabel, .nodeLabel p { font-weight: 600; }",
|
||||
".edgeLabel, .edgeLabel p { color: var(--md-default-fg-color--light) !important; font-size: 13px; font-weight: 500; }",
|
||||
|
||||
// boxes: 1.5 px outline, 6 px corners; "(...)" nodes stay rounder than "[...]" nodes
|
||||
".node rect, .node path, .node polygon, .node circle, .node ellipse { stroke-width: 1.5px !important; }",
|
||||
".node rect:not([rx]) { rx: 6px; ry: 6px; }",
|
||||
".node rect[rx] { rx: 14px; ry: 14px; }",
|
||||
|
||||
// decisions: solid blue hexagons with white text
|
||||
".node.nj-decision polygon { fill: var(--nj-band) !important; stroke: var(--nj-band) !important; stroke-linejoin: round; }",
|
||||
".node.nj-decision .nodeLabel, .node.nj-decision .nodeLabel p { color: #fff !important; }",
|
||||
|
||||
// lines: 1.5 px, solid unless written as -.-
|
||||
".edgePath .path, .flowchart-link { stroke-width: 1.5px !important; }",
|
||||
"marker path { stroke: none !important; }",
|
||||
|
||||
// class diagrams; a node with its own fill ("style X fill:#CCCCFF" on the exception pages) is the highlighted
|
||||
// one and gets soft blue, which also works in dark mode
|
||||
"g.classGroup rect, .classGroup line, g.node.default > g > path { stroke-width: 1.5px !important; }",
|
||||
".node path[style*='fill'], .node rect[style*='fill'] { fill: var(--nj-blue-soft) !important; }",
|
||||
".relation { stroke-width: 1.5px !important; }",
|
||||
|
||||
// sequence diagrams
|
||||
".actor { stroke-width: 1.5px !important; rx: 6px; ry: 6px; }",
|
||||
".actor-line { stroke-width: 1px; }",
|
||||
".messageLine0, .messageLine1 { stroke-width: 1.5px !important; }",
|
||||
".note { rx: 6px; ry: 6px; }"
|
||||
].join("\n");
|
||||
|
||||
var options = {
|
||||
theme: "base",
|
||||
fontFamily: "Inter, sans-serif",
|
||||
themeVariables: { fontFamily: "Inter, sans-serif", fontSize: "15px" },
|
||||
flowchart: { curve: "basis", padding: 12, nodeSpacing: 36, rankSpacing: 48, wrappingWidth: 240 }
|
||||
};
|
||||
|
||||
var minScale = 0.8;
|
||||
|
||||
function postProcess(svg) {
|
||||
// shrink to fit the column, but not below minScale
|
||||
svg = svg.replace(/^(<svg[^>]*style="[^"]*)max-width:\s*([\d.]+)px;/, function (all, head, width) {
|
||||
return head + "max-width: " + width + "px; min-width: " + Math.round(width * minScale) + "px;";
|
||||
});
|
||||
// add class "nj-decision" to every flowchart node drawn as a hexagon (a polygon with six corners)
|
||||
return svg.replace(/<g class="node ([^"]*)"([^>]*)><polygon points="([^"]*)"/g, function (all, cls, rest, points) {
|
||||
var corners = points.trim().split(/\s+/).length;
|
||||
return corners === 6 ? '<g class="node nj-decision ' + cls + '"' + rest + '><polygon points="' + points + '"' : all;
|
||||
});
|
||||
}
|
||||
|
||||
function brand(mermaid) {
|
||||
if (!mermaid || typeof mermaid.initialize !== "function" || mermaid.njBrand) {
|
||||
return mermaid;
|
||||
}
|
||||
var initialize = mermaid.initialize.bind(mermaid);
|
||||
var render = mermaid.render.bind(mermaid);
|
||||
mermaid.initialize = function (config) {
|
||||
config = Object.assign({}, config || {});
|
||||
config.theme = options.theme;
|
||||
config.fontFamily = options.fontFamily;
|
||||
config.themeVariables = Object.assign({}, config.themeVariables, options.themeVariables);
|
||||
config.flowchart = Object.assign({}, config.flowchart, options.flowchart);
|
||||
config.themeCSS = (config.themeCSS || "") + "\n" + css;
|
||||
return initialize(config);
|
||||
};
|
||||
mermaid.render = function () {
|
||||
return render.apply(null, arguments).then(function (result) {
|
||||
result.svg = postProcess(result.svg);
|
||||
return result;
|
||||
});
|
||||
};
|
||||
mermaid.njBrand = true;
|
||||
return mermaid;
|
||||
}
|
||||
|
||||
// Mermaid ends with globalThis["mermaid"] = ..., which runs the setter below
|
||||
var current = brand(window.mermaid);
|
||||
Object.defineProperty(window, "mermaid", {
|
||||
configurable: true,
|
||||
get: function () { return current; },
|
||||
set: function (value) { current = brand(value); }
|
||||
});
|
||||
})();
|
||||
Reference in new issue
Block a user