This commit is contained in:
nlohmann committed 2026-10-27 12:14:00 +00:00
1 parent b36f8a9821
commit 11a863958e
1180 files changed
+14211 -6025

No files matched your search

+32
View File
@@ -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);
}
})();
+106
View File
@@ -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();
}
})();
+1
View File
@@ -0,0 +1 @@
/* figures: exceptions */
+128
View File
@@ -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();
}
})();
+78
View File
@@ -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();
}
})();
+585
View File
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
/* 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 };
})();
+306
View File
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
/* ---------- 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
View File
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
// ---- 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();
}
})();
+100
View File
@@ -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); }
});
})();