@bookbox/core
Version:
Bookbox — e-book format
886 lines (885 loc) • 24.5 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const rootBookHeader = {
value: [],
text: "",
key: "root",
level: 0
};
function getGroup({
condition,
map
}) {
return (schema) => {
const result = [];
const addContents = (schema2) => {
for (const item of schema2) {
if (typeof item === "string") {
continue;
}
if (condition(item)) {
result.push(map(item));
continue;
}
addContents(item.children);
}
};
addContents(schema);
return result;
};
}
function getObjectsByHeader({ condition }) {
return (schema) => {
const result = {};
let currentHeader = "root";
const addContents = (schema2) => {
for (const item of schema2) {
if (typeof item === "string") {
continue;
}
if (item.name === "header") {
currentHeader = item.props.key;
}
if (condition(item)) {
if (!result[currentHeader]) {
result[currentHeader] = [];
}
result[currentHeader].push(item.props.key);
continue;
}
addContents(item.children);
}
};
addContents(schema);
return result;
};
}
const getContents = (builder, store, build) => {
return getGroup({
condition: (item) => item.name === "header" || item.name === "title",
map: (item) => {
const headerProps = item.props;
const header = {
value: builder({ schema: [item], store, build }),
text: item.children.map((value) => typeof value === "string" ? value : JSON.stringify(value)).join(""),
key: headerProps.key,
level: headerProps.level ?? 1
};
return header;
}
});
};
const getItemList = (name) => getGroup({
condition: (e) => e.name === name,
map: (e) => e
});
const getItemMeta = (schema, name) => {
const imageList = getItemList(name)(schema);
return {
keysList: imageList.map((e) => e.props.key ?? ""),
keysByHeader: getObjectsByHeader({
condition: (e) => e.name === name
})(schema)
};
};
function getBookMeta({
schema,
store,
builder,
build
}) {
return {
contents: getContents(builder, store, build)(schema),
media: {
image: getItemMeta(schema, "image"),
video: getItemMeta(schema, "video"),
audio: getItemMeta(schema, "audio")
},
pages: getItemMeta(schema, "page")
};
}
function iterableTreeGetter(options) {
const { getChildren, isLeaf } = options;
return function* iterableTree(schema) {
for (const item of schema) {
yield item;
if (isLeaf(item)) continue;
yield* iterableTree(getChildren(item));
}
};
}
function parseNewLines(newLine) {
return (text) => {
const result = [];
const brs = text.match(/\n\n+/g);
if (!brs) {
return [text];
}
let tail = text;
let i;
for (const br of brs) {
i = tail.indexOf(br);
const subbrs = [];
if (br.length === 2) {
subbrs.push(newLine);
} else {
for (let j = 0; j < 2 * br.length - 4; j++) {
subbrs.push(newLine);
}
}
result.push(tail.slice(0, i), ...subbrs);
tail = tail.slice(i + br.length);
}
if (tail) {
result.push(tail);
}
return result;
};
}
function flatList(elems) {
const stack = [elems];
const result = [];
while (stack.length) {
const current = stack.pop();
if (Array.isArray(current)) {
for (let i = current.length - 1; i >= 0; i--) {
stack.push(current[i]);
}
} else {
result.push(current);
}
}
return result;
}
function getPartialApply(f, args) {
return (...p) => f({ ...p[0] ?? {}, ...args });
}
function getByPath(path, obj) {
return path.reduce((elems, name) => elems[name] ?? {}, obj);
}
function print(schema) {
return schema.map((x) => typeof x === "string" ? x : print(x.children)).join("");
}
function templateToList(text, ...elements) {
const result = [text[0]];
for (let i = 1; i < text.length; i++) {
result.push(elements[i - 1], text[i]);
}
return result;
}
function isTemplateParams(args) {
return args[0] && Array.isArray(args[0]) && "raw" in args[0] && Array.isArray(args[0]["raw"]);
}
function getTextTokens(text) {
const notWords = text.match(/\s+/g);
if (!notWords) {
return [text];
}
const tokens = [];
let tail = text;
for (const notWord of notWords) {
const i = tail.indexOf(notWord);
tokens.push(tail.slice(0, i), notWord);
tail = tail.slice(i + notWord.length);
}
if (tail) {
tokens.push(tail);
}
return tokens;
}
const defaultBlockNameList = [
"strong",
"em",
"header",
"code",
"label",
"link",
"tooltip",
"math",
"external"
];
const defaultBlockNames = new Set(defaultBlockNameList);
function getElementView(item) {
if (typeof item === "string") {
return "inline";
}
const { props: userProps, name } = item;
const props = {
block: defaultBlockNames.has(name),
...userProps
};
if (props.inline) {
return "inline";
}
if (props.block) {
return "block";
}
return props.position === "inline" ? "inline" : "block";
}
const getTextLinkedItem = (text) => ({
bookElementSchema: {
name: "text",
props: {},
children: []
},
raw: text,
previous: null,
next: null,
previousLeaf: null,
nextLeaf: null,
parent: null,
firstChild: null,
lastChild: null,
view: "inline"
});
const getPageLinkedItem = (count) => {
const text = `${count}`;
const textItem = getTextLinkedItem(text);
return {
bookElementSchema: {
name: "page",
props: { count, key: `page-${count}` },
children: []
},
raw: text,
previous: null,
next: null,
previousLeaf: null,
nextLeaf: null,
parent: null,
firstChild: textItem,
lastChild: textItem,
view: "inline"
};
};
function bindList(items) {
for (let i = 1; i < items.length; i++) {
const prev = items[i - 1];
const current = items[i];
current.previous = prev;
prev.next = current;
}
}
function bindLeafList(items) {
for (let i = 1; i < items.length; i++) {
const prev = items[i - 1];
const current = items[i];
current.previousLeaf = prev;
prev.nextLeaf = current;
}
}
function bindParent(items, parent) {
for (const childItem of items) {
childItem.parent = parent;
}
parent.firstChild = items[0] ?? null;
parent.lastChild = items[items.length - 1] ?? null;
}
const isNotEmpty = (text) => text !== "";
function getBookLinkedItem(item) {
if (typeof item === "string") {
const textItem = getTextLinkedItem(item);
const children2 = getTextTokens(item).filter(isNotEmpty).map(getTextLinkedItem);
bindList(children2);
bindParent(children2, textItem);
return textItem;
}
const linkedItem = {
bookElementSchema: item,
firstChild: null,
lastChild: null,
raw: null,
previous: null,
next: null,
previousLeaf: null,
nextLeaf: null,
parent: null,
view: getElementView(item)
};
const children = getBookLinkedSchema(item.children).tree;
bindList(children);
bindParent(children, linkedItem);
return linkedItem;
}
function getLinkedItemList(start, end) {
if (!start) {
return [];
}
const result = [];
let current = start;
while (current && current !== end) {
result.push(current);
current = current.next;
}
if (end) {
result.push(end);
}
return result;
}
function getLeafListLinkedItem(linkedItems) {
const list = [];
for (const item of linkedItems) {
if (item.firstChild === null) {
list.push(item);
} else {
list.push(
...getLeafListLinkedItem(
getLinkedItemList(item.firstChild, item.lastChild)
)
);
}
}
return list;
}
function getBookLinkedSchema(schema, root) {
const linkedSchema = {
start: null,
end: null,
tree: schema.filter(isNotEmpty).map(getBookLinkedItem)
};
bindList(linkedSchema.tree);
if (schema.length === 0 || !root) {
return linkedSchema;
}
const leafList = getLeafListLinkedItem(linkedSchema.tree);
bindLeafList(leafList);
putPages(leafList);
linkedSchema.start = leafList[0];
linkedSchema.end = leafList[leafList.length - 1];
linkedSchema.tree = getLinkedItemList(linkedSchema.tree[0], null);
return linkedSchema;
}
function getLinkedLeafList(linkedSchema) {
const list = [];
let current = linkedSchema.start;
while (current !== null) {
list.push(current);
current = current.nextLeaf;
}
return list;
}
function getSchemaFromLinkedList(linkedList) {
const schema = [];
for (const item of linkedList) {
const { bookElementSchema, raw, firstChild, lastChild } = item;
const { name, props } = bookElementSchema;
if (name === "text" && raw && firstChild === null) {
schema.push(raw);
} else if (name === "text") {
schema.push(
...getSchemaFromLinkedList(getLinkedItemList(firstChild, lastChild))
);
} else {
schema.push({
name,
props,
children: getSchemaFromLinkedList(
getLinkedItemList(firstChild, lastChild)
)
});
}
}
return schema;
}
function putAfterItem(sourceItem, targetItem) {
const { next, nextLeaf, parent } = sourceItem;
targetItem.next = next;
if (next) {
next.previous = targetItem;
}
targetItem.nextLeaf = nextLeaf;
if (nextLeaf) {
nextLeaf.previousLeaf = targetItem;
}
sourceItem.next = targetItem;
targetItem.previous = sourceItem;
sourceItem.nextLeaf = targetItem;
targetItem.previousLeaf = sourceItem;
targetItem.parent = parent;
if (parent && parent.lastChild === sourceItem) {
parent.lastChild = targetItem;
}
}
const PAGE_SIZE = 500;
const NEW_LINE_SIZE = 10;
const NEW_LINE_LONG_SIZE = 50;
function putPages(items) {
if (items.length === 0) {
return;
}
let pageCount = 0;
let currentBlockCount = PAGE_SIZE;
for (const item of items) {
const { raw, view } = item;
const isBlock = view === "block";
let increment = 0;
if (!isBlock && raw) {
const newLines = raw.match(/\n\n*/g) ?? [];
const newLineCount = newLines.reduce((s, e) => s + e.length, 0);
const newLineSize = newLines.reduce(
(s, e) => s + (e.length > 1 ? NEW_LINE_LONG_SIZE : NEW_LINE_SIZE),
0
);
const rawSize = raw.length - newLineCount;
increment = rawSize + newLineSize;
}
currentBlockCount += increment;
if (isBlock || currentBlockCount >= PAGE_SIZE) {
currentBlockCount = 0;
const pageItem = getPageLinkedItem(pageCount++);
putAfterItem(item, pageItem);
}
}
}
function getStore({
builder,
schema,
externalBuilder,
getBuild
}) {
const result = {
dataByKeys: {},
elementsByKeys: getElementsByKeys(schema)
};
const writeDataByKey = (key) => result.dataByKeys[key] = builder({
schema: [result.elementsByKeys[key]],
store: result,
externalBuilder,
build: getBuild(result)
});
const keys = Object.keys(result.elementsByKeys);
for (let g = 0; g < 2; g++) {
for (let i = 0; i < keys.length; i++) {
writeDataByKey(keys[i]);
}
for (let i = keys.length - 1; i >= 0; i--) {
writeDataByKey(keys[i]);
}
}
return result;
}
function getElementsByKeys(schema) {
let result = {};
for (const item of schema) {
if (typeof item === "string") {
continue;
}
result[item.props.key] = item;
const childrenRecord = getElementsByKeys(item.children);
result = {
...result,
...childrenRecord
};
}
return result;
}
const getIterableBook = iterableTreeGetter({
getChildren: (item) => typeof item !== "string" ? item.children : [],
isLeaf: (item) => typeof item === "string"
});
function getResources(schema) {
const result = [];
for (const item of getIterableBook(schema)) {
if (typeof item === "string") continue;
if (!isValidResource(item)) continue;
result.push(item);
}
return result;
}
function isValidResource(resource) {
if (resource.name !== "resource") return false;
const { path, src } = resource.props;
return Boolean(path) && Boolean(src);
}
function getResourceKey({ path, type }) {
return `${type ? `${type}:` : ""}${path}`;
}
function getPathWithPrefix(src, prefix) {
return `${prefix.replace(/\/$/, "")}/${src.replace(/^\.?\//, "")}`;
}
function getActualResourceMap({
schema,
resourceOptions
}) {
const { prefix, pathMap, rawPrefix } = resourceOptions ?? {};
const resources = getResources(schema);
const actualMap = /* @__PURE__ */ new Map();
for (const resource of resources) {
const { path, src, type } = resource.props;
const key = getResourceKey({ path, type });
let settings = { src };
const customSettings = pathMap == null ? void 0 : pathMap.get(key);
if (customSettings) {
settings = customSettings;
} else if (prefix) {
settings = { src: getPathWithPrefix(src, prefix) };
} else if (rawPrefix) {
settings = { src: `${rawPrefix}${src}` };
}
actualMap.set(key, settings);
}
return actualMap;
}
function calculateResources(schema, resourceMap = /* @__PURE__ */ new Map()) {
var _a, _b;
for (const item of schema) {
if (typeof item === "string") continue;
if ("src" in item.props && typeof item.props.src === "string" && item.props.src.startsWith("/")) {
const localPath = item.props.src;
const resourcePath = getResourceKey({ type: item.name, path: localPath });
const typeSrc = (_a = resourceMap.get(resourcePath)) == null ? void 0 : _a.src;
const src = (_b = resourceMap.get(localPath)) == null ? void 0 : _b.src;
if (!src && !typeSrc) {
console.error(`Unknown resource ${item.props.src}`);
}
item.props.src = typeSrc ?? src ?? item.props.src;
}
calculateResources(item.children, resourceMap);
}
}
function getInitial(type = "number", custom) {
switch (type) {
case "number": {
if (custom === void 0) return 1;
const n = typeof custom === "number" ? custom : parseFloat(custom);
if (Number.isNaN(n)) return 1;
return n;
}
case "char": {
if (custom === void 0 || typeof custom !== "string" || custom.length !== 1) return "a".charCodeAt(0);
return custom.charCodeAt(0);
}
case "latin": {
return "a".charCodeAt(0);
}
case "big-latin": {
return "A".charCodeAt(0);
}
case "roman": {
return "ⅰ".charCodeAt(0);
}
case "big-roman": {
return "Ⅰ".charCodeAt(0);
}
case "cyrillic": {
return "а".charCodeAt(0);
}
case "big-cyrillic": {
return "А".charCodeAt(0);
}
default: {
return 1;
}
}
}
function calculateCounters(schema, counters = /* @__PURE__ */ new Map(), steps = /* @__PURE__ */ new Map(), bumped = /* @__PURE__ */ new Set()) {
for (const item of schema) {
if (typeof item === "string") {
continue;
}
if (item.name !== "counter") {
calculateCounters(item.children, counters, steps, bumped);
continue;
}
const props = item.props;
const fillCounter = (name, bump = false) => {
const [value, isUnicodeChar] = counters.get(name) ?? [1, false];
let targetValue = value;
if (bump && bumped.has(name)) {
const step = steps.get(name) ?? 1;
const newValue = value + step;
counters.set(name, [newValue, isUnicodeChar]);
targetValue = newValue;
}
item.children = [isUnicodeChar ? String.fromCharCode(targetValue) : `${targetValue}`];
};
if (props.start) {
const type = props.type ?? "number";
const isUnicodeChar = type !== "number";
const initial = getInitial(type, props.initial);
counters.set(props.start, [initial, isUnicodeChar]);
if (props.step) {
steps.set(props.start, props.step);
}
continue;
}
if (props.end) {
counters.delete(props.end);
steps.delete(props.end);
bumped.delete(props.end);
continue;
}
if (props.use) {
fillCounter(props.use, true);
bumped.add(props.use);
continue;
}
if (props.last) {
fillCounter(props.last);
}
}
}
function expandMarkers(schema) {
const result = [];
const stack = [];
const getTarget = () => stack.length > 0 ? stack[stack.length - 1].children : result;
const push = (item) => getTarget().push(item);
for (const item of schema) {
if (typeof item === "string") {
push(item);
} else if (item.marker === "start") {
delete item.marker;
stack.push(item);
} else if (item.marker === "end") {
const elem = stack.pop();
if (elem) push(elem);
} else {
item.children = expandMarkers(item.children);
push(item);
}
}
while (stack.length > 0) {
const elem = stack.pop();
push(elem);
}
return result;
}
function mergeText(schema) {
const result = [];
let texts = [];
const saveText = () => {
if (texts.length > 0) {
let last = "";
let chainText = "";
const chains = [];
for (const text2 of texts) {
if (last !== null && text2 === null) {
chainText = last.endsWith("\n") ? last.slice(0, last.length - 1) : last;
chains.push(chainText);
} else if (last === null && text2 !== null && text2 !== "\n") {
chainText = text2.startsWith("\n") ? text2.slice(1) : text2;
chains.push(chainText);
continue;
} else if (last !== null && text2 !== null) {
chains.push(last);
} else if (last === null && text2 === "\n") {
continue;
}
last = text2;
}
if (last !== null) chains.push(last);
const text = chains.join("").replace(/\n\n\n\n+/g, "\n\n\n");
if (text !== "") result.push(text);
texts = [];
}
};
for (const item of schema) {
if (typeof item === "string") {
if (item !== "") {
if (texts.length > 0 && texts[texts.length - 1] !== null) {
texts[texts.length - 1] += item;
} else {
texts.push(item);
}
}
} else if (item.name === "hole") {
texts.push(null);
} else {
saveText();
item.children = mergeText(item.children);
result.push(item);
}
}
saveText();
return result;
}
function addKeysToSchema(schema, keys = /* @__PURE__ */ new Map()) {
const bumpKey = (name) => {
keys.set(name, (keys.get(name) ?? 0) + 1);
};
for (const item of schema) {
if (typeof item === "string") {
continue;
}
if (item.props.key === void 0) {
bumpKey(item.name);
item.props = {
...item.props,
key: `${item.name}-${keys.get(item.name)}`
};
}
addKeysToSchema(item.children, keys);
}
}
const prepareBookElem = {
code: (item) => {
if ((item.props.block || item.props.position !== "inline") && item.children.length === 1 && typeof item.children[0] === "string") {
item.children[0] = item.children[0].trim();
}
}
};
function prepareSchema(schema) {
for (const item of getIterableBook(schema)) {
if (typeof item === "string") continue;
const { name } = item;
if (name in prepareBookElem) {
prepareBookElem[name](item);
}
}
}
function createBook({
schema: originalSchema,
builder: builderParams,
externalBuilder,
resourceOptions
}) {
let schema = JSON.parse(JSON.stringify(originalSchema));
schema = expandMarkers(schema);
addKeysToSchema(schema);
calculateCounters(schema);
calculateResources(schema, getActualResourceMap({ schema, resourceOptions }));
prepareSchema(schema);
schema = mergeText(schema);
const linkedSchema = getBookLinkedSchema(schema, true);
const tokensSchema = getSchemaFromLinkedList(linkedSchema.tree);
const builder = createBookBuilder(builderParams);
const getBuild = (currentStore) => (localSchema) => builder({ schema: localSchema, store: currentStore, externalBuilder, build: getBuild(currentStore) });
const store = getStore({ builder, schema, externalBuilder, getBuild });
const build = getBuild(store);
const meta = getBookMeta({ schema, store, builder, build });
schema = tokensSchema;
schema = mergeText(schema);
return {
tokens: builder({ schema, store, externalBuilder, build }),
meta,
store
};
}
const synteticElementNameSet = /* @__PURE__ */ new Set(["text", "page", "error", "empty"]);
const emptyElementNameSet = /* @__PURE__ */ new Set(["resource"]);
function createBookBuilder({
elements,
synteticElements
}) {
const builder = ({ schema, store, externalBuilder, parents = [], build }) => {
const getItemBuilder = (store2) => (item) => {
if (typeof item === "string") {
const content = synteticElements.text({ raw: item, key: "" })({
children: [],
store: store2,
parents,
build
});
if (item.includes("Полнота системы связок")) {
console.log({ item, content });
}
return content;
}
let { name } = item;
if (emptyElementNameSet.has(name) || item.props.hidden) {
name = "empty";
}
let targetElements = elements;
if (synteticElementNameSet.has(name)) {
targetElements = synteticElements;
}
let elemBuilder;
if (name === "external") {
if (externalBuilder) {
const { scope = "custom", name: extName = "local" } = item.props;
elemBuilder = getByPath([`${scope}`, `${extName}`], externalBuilder);
}
if (typeof elemBuilder !== "function") {
elemBuilder = getByPath([name], elements);
}
} else {
const path = name.split(".");
elemBuilder = getByPath(path, targetElements);
}
const getError = (children, error) => {
elemBuilder = synteticElements.error;
return synteticElements.error({
props: item.props,
name: item.name,
error: error ?? "error"
})({
children,
store: store2,
parents,
build
});
};
let childTokens = [];
try {
childTokens = builder({
schema: item.children,
store: store2,
externalBuilder,
parents: [...parents, `${item.props.key}`],
build
});
} catch (e) {
console.error(e);
return getError([], String(e));
}
if (typeof elemBuilder !== "function") {
return getError(childTokens, "unknown element");
}
try {
return elemBuilder(item.props)({
children: childTokens,
store: store2,
parents,
build
});
} catch (e) {
console.error(e);
return getError([], String(e));
}
};
const itemBuilder = getItemBuilder(store);
return flatList(schema.map(itemBuilder));
};
return builder;
}
function createBookExternalBuilder({ element }) {
return { externalBuilder: element };
}
function getLayoutView(props) {
if (props.inline) {
return "inline";
}
if (props.block) {
return "block";
}
return props.position === "inline" ? "inline" : "block";
}
exports.createBook = createBook;
exports.createBookBuilder = createBookBuilder;
exports.createBookExternalBuilder = createBookExternalBuilder;
exports.defaultBlockNameList = defaultBlockNameList;
exports.defaultBlockNames = defaultBlockNames;
exports.expandMarkers = expandMarkers;
exports.flatList = flatList;
exports.getBookLinkedItem = getBookLinkedItem;
exports.getBookLinkedSchema = getBookLinkedSchema;
exports.getBookMeta = getBookMeta;
exports.getByPath = getByPath;
exports.getElementView = getElementView;
exports.getElementsByKeys = getElementsByKeys;
exports.getIterableBook = getIterableBook;
exports.getLayoutView = getLayoutView;
exports.getLinkedLeafList = getLinkedLeafList;
exports.getPageLinkedItem = getPageLinkedItem;
exports.getPartialApply = getPartialApply;
exports.getSchemaFromLinkedList = getSchemaFromLinkedList;
exports.getStore = getStore;
exports.getTextLinkedItem = getTextLinkedItem;
exports.getTextTokens = getTextTokens;
exports.isTemplateParams = isTemplateParams;
exports.iterableTreeGetter = iterableTreeGetter;
exports.mergeText = mergeText;
exports.parseNewLines = parseNewLines;
exports.print = print;
exports.rootBookHeader = rootBookHeader;
exports.templateToList = templateToList;