@diplodoc/markdown-translation
Version:
markdown translation utilities
1,690 lines (1,637 loc) • 54.5 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
compose: () => compose,
extract: () => extract
});
module.exports = __toCommonJS(src_exports);
// src/skeleton/index.ts
var import_assert5 = require("assert");
var import_markdown_it5 = __toESM(require("markdown-it"));
var import_markdown_it_custom_renderer4 = require("@diplodoc/markdown-it-custom-renderer");
var import_markdown_it_meta = __toESM(require("markdown-it-meta"));
var import_markdown_it_sup = __toESM(require("markdown-it-sup"));
var import_notes = __toESM(require("@diplodoc/transform/lib/plugins/notes"));
var import_cut = __toESM(require("@diplodoc/transform/lib/plugins/cut"));
var import_checkbox = __toESM(require("@diplodoc/transform/lib/plugins/checkbox"));
var import_monospace = __toESM(require("@diplodoc/transform/lib/plugins/monospace"));
var import_imsize = __toESM(require("@diplodoc/transform/lib/plugins/imsize"));
var import_file2 = __toESM(require("@diplodoc/transform/lib/plugins/file"));
var import_video2 = __toESM(require("@diplodoc/transform/lib/plugins/video"));
var import_table2 = __toESM(require("@diplodoc/transform/lib/plugins/table"));
// src/utils.ts
var import_markdown_it = __toESM(require("markdown-it"));
var md = new import_markdown_it.default();
var state = new md.core.State("", md, {});
function token(type, props = {}) {
return Object.assign(new state.Token(type, "", 0), props);
}
// src/skeleton/plugins/includes.ts
var INCLUDE_REGEXP = /^{%\s*include\s*(notitle)?\s*\[(.+?)]\((.+?)\)\s*%}$/;
function includes_default(md2) {
const plugin = (state2) => {
const { tokens } = state2;
let i = 0;
while (i < tokens.length) {
const openToken = tokens[i];
const contentToken = tokens[i + 1];
const closeToken = tokens[i + 2];
if (openToken.type === "paragraph_open" && contentToken.type === "inline" && contentToken.content.match(INCLUDE_REGEXP) && closeToken.type === "paragraph_close") {
contentToken.children = [
token("liquid", {
content: "",
skip: contentToken.content,
subtype: "Include"
})
];
i += 3;
} else {
i++;
}
}
};
try {
md2.core.ruler.before("curly_attributes", "includes", plugin);
} catch (e) {
md2.core.ruler.push("includes", plugin);
}
}
// src/skeleton/hooks/index.ts
var import_markdown_it_custom_renderer3 = require("@diplodoc/markdown-it-custom-renderer");
// src/skeleton/consumer.ts
var import_assert4 = require("assert");
var import_sentenizer = require("@diplodoc/sentenizer");
// src/xliff/renderer/md-xliff/index.ts
var import_markdown_it4 = __toESM(require("markdown-it"));
var import_markdown_it_custom_renderer2 = require("@diplodoc/markdown-it-custom-renderer");
// src/xliff/renderer/md-xliff/hooks/index.ts
var import_markdown_it_custom_renderer = require("@diplodoc/markdown-it-custom-renderer");
// src/xliff/renderer/md-xliff/hooks/after-inline.ts
var import_markdown_it2 = __toESM(require("markdown-it"));
// src/xliff/symbols.ts
var import_crypto = __toESM(require("crypto"));
var gt = import_crypto.default.randomUUID();
var lt = import_crypto.default.randomUUID();
var sl = import_crypto.default.randomUUID();
var qt = import_crypto.default.randomUUID();
var gtre = new RegExp(`${gt}`, "gmu");
var ltre = new RegExp(`${lt}`, "gmu");
var qtre = new RegExp(`${qt}`, "gmu");
var slre = new RegExp(`${sl}`, "gmu");
function unescapeSymbols(str) {
return str.replace(gtre, ">").replace(ltre, "<").replace(qtre, '"').replace(slre, "/");
}
// src/xliff/renderer/md-xliff/hooks/after-inline.ts
var escapeHTML = new import_markdown_it2.default().utils.escapeHtml;
function afterInline(parameters) {
if (!parameters.rendered) {
return "";
}
let rendered = parameters.rendered.join("");
if (!rendered.length) {
return "";
}
rendered = escapeHTML(rendered);
rendered = unescapeSymbols(rendered);
parameters.rendered.splice(0, parameters.rendered.length, rendered);
return "";
}
// src/xliff/renderer/md-xliff/hooks/index.ts
var hooks = {
[import_markdown_it_custom_renderer.CustomRendererLifeCycle.AfterInlineRender]: [afterInline]
};
// src/xliff/renderer/md-xliff/rules/diplodoc/index.ts
var always = (a) => () => a;
var alwaysEmptyString = always("");
var rules = {
// notes
yfm_note_open: alwaysEmptyString,
yfm_note_close: alwaysEmptyString,
yfm_note_title_open: alwaysEmptyString,
yfm_note_title_close: alwaysEmptyString,
yfm_note_content_open: alwaysEmptyString,
yfm_note_content_close: alwaysEmptyString,
// cuts
yfm_cut_open: alwaysEmptyString,
yfm_cut_title_open: alwaysEmptyString,
yfm_cut_title_close: alwaysEmptyString,
yfm_cut_content_open: alwaysEmptyString,
yfm_cut_content_close: alwaysEmptyString,
yfm_cut_close: alwaysEmptyString,
// gfm tables
table_open: alwaysEmptyString,
thead_open: alwaysEmptyString,
tr_open: alwaysEmptyString,
tr_close: alwaysEmptyString,
th_open: alwaysEmptyString,
th_close: alwaysEmptyString,
thead_close: alwaysEmptyString,
tbody_open: alwaysEmptyString,
tbody_close: alwaysEmptyString,
td_open: alwaysEmptyString,
td_close: alwaysEmptyString,
table_close: alwaysEmptyString,
// checkbox
checkbox_open: alwaysEmptyString,
checkbox_input: alwaysEmptyString,
checkbox_label_open: alwaysEmptyString,
checkbox_label_close: alwaysEmptyString,
checkbox_close: alwaysEmptyString,
// monospace
monospace_open: alwaysEmptyString,
monospace_close: alwaysEmptyString,
// include
include: alwaysEmptyString,
// tabs
tabs_open: alwaysEmptyString,
tabs_close: alwaysEmptyString,
"tab-list_open": alwaysEmptyString,
"tab-list_close": alwaysEmptyString,
tab_open: alwaysEmptyString,
tab_close: alwaysEmptyString,
"tab-panel_open": alwaysEmptyString,
"tab-panel_close": alwaysEmptyString,
// anchors
span_open: alwaysEmptyString,
span_close: alwaysEmptyString,
// table
yfm_tbody_open: alwaysEmptyString,
yfm_tbody_close: alwaysEmptyString,
yfm_table_open: alwaysEmptyString,
yfm_table_close: alwaysEmptyString,
yfm_tr_open: alwaysEmptyString,
yfm_tr_close: alwaysEmptyString,
yfm_td_open: alwaysEmptyString,
yfm_td_close: alwaysEmptyString
};
// src/xliff/generator/template.ts
var import_assert = require("assert");
var import_i18n_iso_languages = __toESM(require("@cospired/i18n-iso-languages"));
var import_i18n_iso_countries = __toESM(require("@shellscape/i18n-iso-countries"));
var languagesList = import_i18n_iso_languages.default.langs();
function unit(source, index) {
return `
<trans-unit id="${index + 1}">
${source}
</trans-unit>
`.trim();
}
function generate(parameters, units) {
validateParams(parameters);
const { source, target, skeletonPath, markdownPath } = parameters;
return `
<?xml version="1.0" encoding="UTF-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file original="${markdownPath}" source-language="${source.language}-${source.locale}" target-language="${target.language}-${target.locale}" datatype="markdown">
<header>
<skeleton>
<external-file href="${skeletonPath}"></external-file>
</skeleton>
</header>
<body>
${units.map(unit).join("\n ")}
</body>
</file>
</xliff>
`.trim();
}
function validateParams(parameters) {
const { source, target } = parameters;
(0, import_assert.ok)(validLanguageLocale(source), "Invalid source language locale pair.");
(0, import_assert.ok)(validLanguageLocale(target), "Invalid target language locale pair.");
}
function validLanguageLocale(parameters) {
const { language, locale } = parameters;
return import_i18n_iso_languages.default.isValid(language) && import_i18n_iso_countries.default.isValid(locale);
}
// src/xliff/generator/trans-unit.ts
function generateTransUnit(parameters) {
const { source, sourceLangLocale, target, targetLangLocale } = parameters;
let rendered = ``;
if (target == null ? void 0 : target.length) {
rendered += "\n";
rendered += `<target`;
if (targetLangLocale == null ? void 0 : targetLangLocale.length) {
rendered += ` xml:lang="${targetLangLocale}"`;
}
rendered += ">";
rendered += `${target}</target>`;
}
if (source) {
rendered += "\n";
rendered += `<source`;
if (sourceLangLocale == null ? void 0 : sourceLangLocale.length) {
rendered += ` xml:lang="${sourceLangLocale}"`;
}
rendered += ">";
rendered += `${source}</source>`;
}
return rendered.trim();
}
// src/xliff/generator/g.ts
function generateOpenG(parameters) {
const props = Object.keys(parameters).map((key) => `${key}=${qt}${parameters[key]}${qt}`).join(" ");
return `${lt}g ${props}${gt}`;
}
function generateCloseG() {
return `${lt}${sl}g${gt}`;
}
// src/xliff/generator/x.ts
function generateX(parameters) {
const { ctype, equivText } = parameters;
let rendered = `${lt}x`;
if (ctype == null ? void 0 : ctype.length) {
rendered += ` ctype=${qt}${ctype}${qt}`;
}
if (equivText == null ? void 0 : equivText.length) {
rendered += ` equiv-text=${qt}${equivText}${qt}`;
}
if ((ctype == null ? void 0 : ctype.length) || (equivText == null ? void 0 : equivText.length)) {
rendered += " ";
}
rendered += sl + gt;
return rendered;
}
// src/xliff/renderer/md-xliff/rules/link.ts
var link = {
link_auto: linkAuto,
link_open: linkOpen,
link_close: linkClose
};
function linkAuto(tokens, i) {
const href = tokens[i].content;
return generateX({
ctype: "link_autolink",
equivText: `${href}`
});
}
function linkOpen(tokens, i) {
const open = tokens[i];
if (open.g) {
const title = open.attrGet("title");
const href = open.attrGet("href");
const begin = "[" + (open.reflink ? "{#T}" : "");
const end = "](" + [
href,
title && '"' + title + '"'
].filter(Boolean).join(" ") + ")";
return generateOpenG({
ctype: "link",
equivText: `${begin}{{text}}${end}`,
"x-begin": begin,
"x-end": end
});
} else {
if (open.reflink) {
return "";
}
return generateX({
ctype: "link_text_part_open",
equivText: "["
});
}
}
function linkClose(tokens, i) {
const close = tokens[i];
if (close.g) {
return generateCloseG();
}
const open = this.state.link.map.get(close);
const title = open.attrGet("title");
const href = open.attrGet("href");
let rendered = "";
if (open.reflink) {
rendered += generateX({
ctype: "link_reflink",
equivText: "[{#T}]"
});
} else {
rendered += generateX({
ctype: "link_text_part_close",
equivText: "]"
});
}
rendered += generateX({
ctype: "link_attributes_part_open",
equivText: "("
});
if (href == null ? void 0 : href.length) {
rendered += generateX({
ctype: "link_attributes_href",
equivText: href
});
}
if (title == null ? void 0 : title.length) {
rendered += generateX({
ctype: "link_attributes_title",
equivText: title
});
}
rendered += generateX({
ctype: "link_attributes_part_close",
equivText: ")"
});
return rendered;
}
// src/xliff/renderer/md-xliff/rules/pair.ts
var pair = {
strong_open: pairOpen,
strong_close: pairOpen,
em_open: pairOpen,
em_close: pairOpen,
s_open: pairOpen,
s_close: pairOpen,
sup_open: pairOpen,
sup_close: pairOpen,
monospace_open: pairOpen,
monospace_close: pairOpen
};
function pairOpen(tokens, i) {
const { markup, tag, type } = tokens[i];
if (!(markup == null ? void 0 : markup.length)) {
throw new Error(`markup missing for token: ${type}`);
}
if (!(tag == null ? void 0 : tag.length)) {
throw new Error(`tag missing for token: ${type}`);
}
const [_, tagType] = type.split("_");
return generateX({
ctype: `${tag}_${tagType}`,
equivText: markup
});
}
// src/xliff/renderer/md-xliff/rules/code-inline.ts
var codeInline = {
code_inline_open: codeInlineRule("open"),
code_inline_close: codeInlineRule("close")
};
function codeInlineRule(dir) {
return function(tokens, i) {
const { markup } = tokens[i];
return generateX({
ctype: `code_${dir}`,
equivText: markup
});
};
}
// src/xliff/renderer/md-xliff/rules/text.ts
var text = {
text: function(tokens, i) {
var _a;
const text3 = tokens[i];
if (!((_a = text3.content) == null ? void 0 : _a.length) || text3.reflink) {
return "";
}
return text3.content;
}
};
// src/xliff/renderer/md-xliff/rules/liquid.ts
var liquid = {
liquid: function(tokens, i) {
const { subtype, content, markup } = tokens[i];
return generateX({
ctype: `liquid_${subtype}`,
equivText: markup || content
});
}
};
// src/xliff/renderer/md-xliff/rules/image.ts
var import_markdown_it3 = __toESM(require("markdown-it"));
var decodeURL = new import_markdown_it3.default().utils.lib.mdurl.decode;
var image = {
image_open: imageOpen,
image_close: imageClose
};
function imageOpen(tokens, idx) {
const open = tokens[idx];
if (open.g) {
const close = open.g;
const src = close.attrGet("src");
const title = close.attrGet("title");
const height = close.attrGet("height");
const width = close.attrGet("width");
const begin = ",
title && '"' + title + '"',
size(width, height)
].filter(Boolean).join(" ") + ")";
return generateOpenG({
ctype: "image",
equivText: `${begin}{{text}}${end}`,
"x-begin": begin,
"x-end": end
});
} else {
return generateX({
ctype: "image_text_part_open",
equivText: "!["
});
}
}
function imageClose(tokens, idx) {
const close = tokens[idx];
if (close.g) {
return generateCloseG();
}
let rendered = "";
rendered += generateX({
ctype: "image_text_part_close",
equivText: "]"
});
rendered += generateX({
ctype: "image_attributes_part_open",
equivText: "("
});
let src = close.attrGet("src");
if (src == null ? void 0 : src.length) {
src = decodeURL(src);
rendered += generateX({
ctype: "image_attributes_src",
equivText: src
});
}
const title = close.attrGet("title");
if (title == null ? void 0 : title.length) {
rendered += generateX({
ctype: "image_attributes_title",
equivText: title
});
}
const height = close.attrGet("height");
const width = close.attrGet("width");
if ((width == null ? void 0 : width.length) || (height == null ? void 0 : height.length)) {
rendered += generateX({
ctype: "image_attributes_size",
equivText: size(width, height)
});
}
rendered += generateX({
ctype: "image_attributes_part_close",
equivText: ")"
});
return rendered;
}
function size(width, height) {
if (!(width == null ? void 0 : width.length) && !(height == null ? void 0 : height.length)) {
return "";
}
let equivText = "=";
if (width == null ? void 0 : width.length) {
equivText += width;
}
equivText += "x";
if (height == null ? void 0 : height.length) {
equivText += height;
}
return equivText;
}
// src/xliff/renderer/md-xliff/rules/video.ts
var video = {
video: videoRule
};
function videoRule(tokens, i) {
const { service, videoID } = tokens[i];
return generateX({ ctype: "video", equivText: `@[${service}](${videoID})` });
}
// src/xliff/renderer/md-xliff/rules/file.ts
var file = {
yfm_file: fileRule
};
var attributes = /* @__PURE__ */ new Map([
["href", "src"],
["download", "name"],
["hreflang", "lang"],
["type", "type"],
["target", "target"],
["rel", "rel"],
["referrerpolicy", "referrerpolicy"]
]);
var translatable = /* @__PURE__ */ new Set(["download"]);
function fileRule(tokens, i) {
const { attrs } = tokens[i];
if (!(attrs == null ? void 0 : attrs.length)) {
throw new Error(`failed to render token: ${tokens[i]}`);
}
let rendered = generateX({
ctype: "file_open",
equivText: "{%"
});
for (const [key, val] of attrs) {
const attribute = attributes.get(key);
if (!(attribute == null ? void 0 : attribute.length)) {
continue;
}
const ctype = `file_${attribute}`;
const equivText = `${attribute}="${val}"`;
if (translatable.has(key)) {
rendered += generateX({
ctype: `${ctype}_open`,
equivText: `${attribute}="`
});
rendered += val;
rendered += generateX({
ctype: `${ctype}_close`,
equivText: '"'
});
} else {
rendered += generateX({ ctype, equivText });
}
}
rendered += generateX({
ctype: "file_close",
equivText: "%}"
});
return rendered;
}
// src/xliff/renderer/md-xliff/rules/html-inline.ts
var htmlInline = {
html_inline: function(tokens, i) {
const { content, type } = tokens[i];
return generateX({ ctype: type, equivText: content });
}
};
// src/xliff/renderer/md-xliff/rules/index.ts
var rules2 = {
code_block: () => "",
fence: () => "",
hardbreak: () => "\n",
softbreak: () => "\n",
html_block: () => "",
heading_open: () => "",
heading_close: () => "",
paragraph_open: () => "",
paragraph_close: () => "",
bullet_list_open: () => "",
bullet_list_close: () => "",
ordered_list_open: () => "",
ordered_list_close: () => "",
blockquote_open: () => "",
blockquote_close: () => "",
list_item_open: () => "",
list_item_close: () => "",
...rules,
...pair,
...codeInline,
...link,
...text,
...liquid,
...image,
...video,
...file,
...htmlInline
};
// src/xliff/renderer/md-xliff/index.ts
function render(tokens, state2, parameters) {
const xliffRenderer = new import_markdown_it4.default({ html: true });
xliffRenderer.use(import_markdown_it_custom_renderer2.customRenderer, {
rules: rules2,
hooks,
initState: () => state2
});
const source = xliffRenderer.renderer.render([token("inline", {
children: groupUselessTokens(tokens)
})], xliffRenderer.options, {
source: []
});
return generateTransUnit({
source,
id: parameters.unitId
});
}
function groupUselessTokens(tokens) {
const map = {};
const result = [];
for (const part of tokens) {
if (!part.content) {
const [name, type] = part.type.split("_");
if (type === "open") {
map[part.type] = map[part.type] || [];
map[part.type].push(part);
} else if (type === "close") {
map[name + "_open"] = map[name + "_open"] || [];
const opener = map[name + "_open"].pop();
if (opener) {
opener.g = part;
part.g = opener;
}
}
}
result.push(part);
}
return result;
}
// src/xliff/parser/index.ts
var import_assert2 = __toESM(require("assert"));
var import_fast_xml_parser = require("fast-xml-parser");
var import_cheerio = require("cheerio");
var import_domhandler = require("domhandler");
var selfClosingTags = /* @__PURE__ */ new Set(["x"]);
function parse(parameters) {
validateParams2(parameters);
const { xliff, units, useSource = false } = parameters;
if (units) {
return parseTargets(units.map((unit2) => selectTargets(unit2, useSource).get(0)));
}
const targets = selectTargets(xliff, useSource);
return parseTargets(targets.get());
}
function selectTargets(xliff, useSource) {
const $ = (0, import_cheerio.load)(xliff, { xml: true });
const targets = $(useSource ? "source" : "target");
(0, import_assert2.ok)(targets.length, "Did not find any translations");
return targets;
}
function parseTargets(targets) {
const parsed = new Array();
const ref = { nodes: [] };
for (const target of targets) {
inorderNodes(target, ref);
const tokens = nodesIntoXLFTokens(ref.nodes);
parsed.push(tokens);
ref.nodes = [];
}
return parsed;
}
function inorderNodes(node, ref) {
if (!node) {
return;
}
if ((0, import_domhandler.isText)(node)) {
ref.nodes.push(node);
}
if ((0, import_domhandler.isTag)(node)) {
if (selfClosingTags.has(node.name)) {
node.attribs.nodeType = "self-closing";
} else {
node.attribs.nodeType = "open";
}
ref.nodes.push(node);
let next = node.firstChild;
while (next) {
inorderNodes(next, ref);
next = next.nextSibling;
}
}
if ((0, import_domhandler.isTag)(node) && !selfClosingTags.has(node.name)) {
const closeNode = node.cloneNode();
closeNode.attribs.nodeType = "close";
closeNode.attribs.g = node;
ref.nodes.push(closeNode);
}
}
function nodesIntoXLFTokens(nodes) {
var _a, _b;
const tokens = new Array();
for (const node of nodes) {
if ((0, import_domhandler.isTag)(node)) {
const nodeType = (_a = node == null ? void 0 : node.attribs) == null ? void 0 : _a.nodeType;
(0, import_assert2.default)(nodeType === "open" || nodeType === "close" || nodeType === "self-closing");
const token2 = {
type: "tag",
data: node.name,
nodeType,
begin: "",
end: ""
};
const syntax = (_b = node == null ? void 0 : node.attribs) == null ? void 0 : _b.ctype;
if (syntax == null ? void 0 : syntax.length) {
token2.syntax = syntax;
}
const equivText = node == null ? void 0 : node.attribs["equiv-text"];
if (equivText == null ? void 0 : equivText.length) {
token2.equivText = equivText;
}
token2.g = node.attribs.g;
token2.begin = node == null ? void 0 : node.attribs["x-begin"];
token2.end = node == null ? void 0 : node.attribs["x-end"];
tokens.push(token2);
} else if ((0, import_domhandler.isText)(node)) {
const token2 = {
type: "text",
data: node.data
};
tokens.push(token2);
}
}
return tokens;
}
function validateParams2(parameters) {
if (parameters.units) {
parameters.units.forEach((unit2) => {
const validation2 = import_fast_xml_parser.XMLValidator.validate(unit2);
if (validation2 !== true) {
console.log("PROBLEM", unit2);
throw validation2.err.msg;
}
});
return;
}
const validation = import_fast_xml_parser.XMLValidator.validate(parameters.xliff);
if (validation !== true) {
throw validation.err.msg;
}
}
// src/xliff/index.ts
var XLF = { render, parse, generate, generateTransUnit };
// src/skeleton/search.ts
var import_assert3 = require("assert");
var searchCommon = (content, from, match) => {
const index = content.indexOf(match, from);
return [index, index + match.length, match];
};
var searchTrimStart = (content, from, match) => {
while (match.startsWith(" ")) {
match = match.slice(1);
const index = content.indexOf(match, from);
if (index > -1) {
return [index, index + match.length, match];
}
}
return [-1, -1, ""];
};
var searchRegExp = (content, from, match) => {
const variant = match.replace(/\\\\/g, "\\");
const index = content.indexOf(variant, from);
return [index, index + variant.length, variant];
};
var searchLinkText = (content, from, match) => {
const variant = match.replace(/(\[|])/g, "\\$1");
const index = content.indexOf(variant, from);
return [index, index + variant.length, variant];
};
var searchMultilineInlineCode = (content, from, match) => {
const parts = match.split(/[\s\n]/g);
let index;
const start = index = content.indexOf(parts.shift(), from);
while (parts.length && index > -1) {
const part = parts.shift();
index = content.indexOf(part, index);
index = index === -1 ? index : index + part.length;
}
if (index === -1) {
return [-1, -1, match];
}
return [start, index, content.slice(start, index)];
};
var search = (content, [start, end], match) => {
const matches = [searchCommon, searchTrimStart, searchRegExp, searchLinkText, searchMultilineInlineCode];
(0, import_assert3.ok)(match, `search aaaaaaaa empty ${match}`);
let from = -1, to = -1, variant = match;
while (matches.length && from === -1) {
start = start === -1 ? 0 : start;
[from, to, variant] = matches.shift()(content, start, match);
}
(0, import_assert3.ok)(from >= start, `search aaaaaaaa start: ${from} > ${start}`);
(0, import_assert3.ok)(to <= end, `search aaaaaaaa end: ${to} <= ${end}`);
return [from, to, variant];
};
// src/skeleton/consumer.ts
var hasContent = (token2) => token2.content;
var notFake = (token2) => !token2.fake;
var br = (token2) => ["hardbreak", "softbreak"].includes(token2.type);
var reflink = (token2) => token2.reflink;
var replace = (from, to, source, past) => {
const start = source.slice(0, from);
const end = source.slice(to);
return start + past + end;
};
var isContentful = (token2) => {
var _a;
return !reflink(token2) && ((_a = token2.content) == null ? void 0 : _a.trim());
};
var firstContentful = (tokens) => {
const index = tokens.findIndex(isContentful);
return index > -1 ? [tokens[index], index] : [null, -1];
};
var lastContentful = (tokens) => {
const index = tokens.findLastIndex(isContentful);
return index > -1 ? [tokens[index], index] : [null, -1];
};
function dropUselessTokens(tokens) {
const [, first] = firstContentful(tokens);
const [, last] = lastContentful(tokens);
if (first === -1) {
return [tokens, [], []];
}
return [tokens.slice(0, first), tokens.slice(first, last + 1), tokens.slice(last + 1)];
}
function eruler(content, [start, end], tokens, action) {
return tokens.reduce(([from, to], token2) => {
if (!token2 || typeof token2 === "object" && !token2.content && !token2.skip) {
return [from, to];
}
const [_from, _to] = action(content, [to, end], token2);
return [from === -1 ? _from : from, _to];
}, [-1, start]);
}
var skip = (content, [start, end], token2) => {
let from = start === -1 ? 0 : start;
let to;
if (Array.isArray(token2)) {
[from, to] = eruler(content, [from, end], token2, skip);
} else if (token2.skip) {
[from, to] = skip(content, [from, end], token2.skip);
} else {
const match = typeof token2 === "string" ? token2 : token2.content;
[from, to] = match ? search(content, [start, end], match) : [from, from];
}
(0, import_assert4.ok)(to <= end, `skip aaaaaaaa end: ${to} <= ${end}`);
return [from, to];
};
var gobble = (content, [start, end], token2) => {
if (token2.skip) {
return skip(content, [start, end], token2);
} else if (token2.content) {
return search(content, [start, end], token2.content);
}
return [-1, -1];
};
function trim(part) {
const [first, iFirst] = firstContentful(part);
if (first) {
part[iFirst] = token(first.type, {
...first,
content: first.content.trimStart(),
generated: (first.generated || "") + "|trimStart"
});
}
const [last, iLast] = lastContentful(part);
if (last) {
part[iLast] = token(last.type, {
...last,
content: last.content.trimEnd(),
generated: (last.generated || "") + "|trimEnd"
});
}
return part;
}
function exclude(content, tokens) {
const part = trim(tokens.filter(hasContent));
const [, to] = eruler(content, [0, content.length], part, gobble);
return content.slice(to);
}
var Consumer = class {
constructor(content, cursor, state2) {
this.content = content;
this.cursor = cursor;
this.state = state2;
}
gap = 0;
limit = Infinity;
token(type, props = {}) {
return token(type, props);
}
skip(part) {
if (part) {
const [from, to] = skip(this.content, [this.cursor, this.limit], part);
if (from > -1) {
this.cursor = to;
}
}
return this;
}
window(map, gap) {
map = map || [1, this.state.lines.end.length];
const [start, end] = [
this.state.lines.start[map[0] - 1] + gap,
this.state.lines.end[map[1] - 1] + gap
];
if (start >= this.cursor) {
this.cursor = start;
this.limit = end;
}
}
process = (tokens) => {
tokens = [].concat(tokens);
const parts = this.split(tokens);
return parts.map(this.consume).filter(Boolean);
};
consume = (part) => {
let past;
const [before, tokens, after] = dropUselessTokens(part);
this.handleHooks("before", before);
this.skip(before);
this.handleHooks("after", before);
if (tokens.length === 1 && tokens[0].type !== "text") {
tokens[0] = this.token("text", {
content: tokens[0].content
});
}
if (tokens.length) {
past = `%%%${this.state.skeleton.id}%%%`;
this.replace(tokens, past);
const xliff = XLF.render(tokens, this.state, {
unitId: this.state.skeleton.id++,
lang: "ru"
});
this.state.segments.push(xliff);
}
this.handleHooks("before", after);
this.skip(after);
this.handleHooks("after", after);
return past ? { part, past } : null;
};
replace(tokens, past) {
if (!tokens.length) {
return;
}
const [start, end] = eruler(
this.content,
[this.cursor, this.limit],
tokens,
(content, [start2, end2], token2) => {
const [from, to, match] = gobble(content, [start2, end2], token2);
if (match) {
token2.content = match;
}
return [from, to];
}
);
const gap = -(end - start - past.length);
this.content = replace(start, end, this.content, past);
this.cursor = start + past.length;
this.limit += gap;
this.gap += gap;
}
/**
* Split inline tokens sequence on parts,
* where each part is equal to one sentense of inline fragment.
* Trim useless spaces.
*
* Some **sentense**. Other sentense.
* ^--------------------------------^ inline fragment
* ^-1-^ 2^--3---^ 4^5^------6------^ tokens
* ^----------------^ ^-------------^ parts
*/
split(tokens) {
const parts = [];
let content = "";
let part = [];
const add = (token2) => token2 && (token2.content || token2.skip) && part.push(token2);
const release = () => {
if (part.length) {
parts.push(trim(part.filter(notFake)));
part = [];
content = "";
}
};
for (const token2 of tokens) {
let fake = null;
if (!hasContent(token2)) {
part.push(token2);
if (br(token2)) {
content += " ";
fake = this.token("text", {
content: " ",
fake: true,
generated: "fake"
});
} else if (token2.markup && !token2.skip) {
content += token2.markup;
fake = this.token("text", {
content: token2.markup,
fake: true,
generated: "fake"
});
} else {
continue;
}
}
content += token2.content || "";
const segments = (0, import_sentenizer.sentenize)(content);
if (segments.length === 1) {
add(fake);
add(token2);
continue;
}
const [head, full, rest] = [segments.shift(), segments, segments.pop()];
add(this.token("text", {
content: exclude(head, part).trimEnd(),
generated: "head"
}));
release();
for (const segment of full) {
add(this.token("text", {
content: segment.trim(),
generated: "leaf"
}));
release();
}
content = ((fake == null ? void 0 : fake.content) || "") + rest;
add(fake);
add(this.token(token2.type, {
...token2,
content: rest,
generated: "rest"
}));
}
release();
return parts;
}
handleHooks(phase, tokens) {
tokens = [].concat(tokens);
tokens.forEach((token2) => {
const handlers = this.state.hooks[phase].get(token2);
handlers.forEach((handler) => handler(this));
});
}
};
// src/liquid/index.ts
var specification_ = [
// Conditions
// If statement
[/^\{%\s*if[^%}]+?\s*%}/, "liquid", "Literal"],
// Else statement
[/^\{%\s*else\s*%\}/, "liquid", "Literal"],
// EndIf statement
[/^\{%\s*endif\s*%\}/, "liquid", "Literal"],
// Changelog
[/^\{%\s*changelog\s*%\}/, "liquid", "Literal"],
// EndChangelog
[/^\{%\s*endchangelog\s*%\}/, "liquid", "Literal"],
// Fake Tabs
[/^\{%\s*list tabs\s*%\}/, "liquid", "Literal"],
// Fake Tabs end
[/^\{%\s*endlist\s*%\}/, "liquid", "Literal"],
// ForInLoop
[/^\{%\s*for\s+[\w.-]+\s+in\s+[\w.-]+\s*%\}/, "liquid", "Literal"],
// EndForInLoop
[/^\{%\s*endfor\s*%\}/, "liquid", "Literal"],
// Function
[/^\{\{\s*[\w.-]+?\(.*?\)\s*\}\}/, "variable", "Function"],
// Filter
[/^\{\{\s*[\w.-]+\s*\|\s*\w+\s*\}\}/, "variable", "Filter"],
// Variable
[/^\{\{\s*[\w.-]+\s*\}\}/, "variable", "Variable"],
// Attributes
[/^\{\s*(?:[.#](?!T})[a-z0-9_-]+|[a-z0-9_-]+\s*=\s*[a-z0-9_-]+)\s*\}/i, "liquid", "Attributes"],
// // Space
// [/^[^\S\r\n]+/, 'text'],
// Newline
[/^[\r\n]+/, "hardbreak"],
// Text
// without grabbing liquid/variable syntax
[/^[\S\s]+?(?={{1,2}|{%)/, "text"],
// plain text
[/^[\S\s]+/, "text"]
];
var Tokenizer = class {
input;
cursor;
specification;
constructor(input, configuration = {}) {
this.input = input;
this.cursor = 0;
const { specification = specification_ } = configuration;
this.specification = specification;
}
tokenize() {
this.cursor = 0;
return Array.from(this);
}
*[Symbol.iterator]() {
let value, done;
do {
({ value, done } = this.next() ?? {});
if (value == null) {
return;
}
yield value;
} while (!done);
}
next() {
const [token2, value] = this.match();
if (!token2) {
return { value: null, done: true };
}
token2.markup = value;
token2.generated = "liquid";
this.cursor += value.length;
return { value: token2, done: this.done() };
}
return() {
return this.next();
}
throw() {
return { value: null, done: true };
}
match() {
const left = this.input.slice(this.cursor);
for (const [regexp, type, subtype] of this.specification) {
const [value] = regexp.exec(left) ?? [null];
if (value === null) {
continue;
}
switch (type) {
case "text":
return [token(type, { content: value }), value];
case "hardbreak":
return [token(type), value];
case "liquid":
return [token("liquid", { content: "", skip: value, subtype }), value];
case "variable":
return [token("liquid", { content: value, subtype }), value];
default:
throw new TypeError("Unexpected liquid token");
}
}
return [null];
}
done() {
return this.cursor === this.input.length;
}
};
// src/skeleton/hooks/meta.ts
function meta() {
const meta3 = this.state.md.meta ?? {};
const consumer = new Consumer(this.state.result, this.state.cursor, this.state);
if (!Object.keys(meta3).length) {
return "";
}
traverse(meta3, (value, key) => {
const tokenizer = new Tokenizer(value);
consumer.skip(key);
consumer.process([...tokenizer.tokenize()]);
});
this.state.result = consumer.content;
this.state.cursor = consumer.cursor;
this.state.gap += consumer.gap;
return "";
}
function traverse(meta3, fn, key) {
if (typeof meta3 === "string") {
fn(meta3, key);
} else if (Array.isArray(meta3)) {
for (const val of meta3) {
traverse(val, fn);
}
} else if (meta3 && typeof meta3 === "object") {
for (const [key2, val] of Object.entries(meta3)) {
traverse(val, fn, key2);
}
}
}
// src/skeleton/hooks/after-inline.ts
function afterInline2(parameters) {
if (!parameters.rendered) {
return "";
}
const consumer = new Consumer(this.state.result, this.state.cursor, this.state);
consumer.window(parameters.map, this.state.gap);
consumer.process(parameters.tokens);
this.state.result = consumer.content;
this.state.cursor = consumer.cursor;
this.state.gap += consumer.gap;
return "";
}
// src/skeleton/hooks/before-inline.ts
function beforeInline(parameters) {
const tokens = parameters.tokens;
for (let idx = 0; idx < tokens.length; idx++) {
if (tokens[idx].type === "code_inline") {
splitInlineCode(tokens, idx);
}
}
return "";
}
function splitInlineCode(tokens, idx) {
const codeInlineOpen = token("code_inline_open", {
content: "",
markup: tokens[idx].markup,
skip: [tokens[idx].markup]
});
const text3 = token("text", {
// stupid special case
content: ["`", "``", "```"].includes(tokens[idx].content) ? " " + tokens[idx].content + " " : tokens[idx].content
});
const codeInlineClose = token("code_inline_close", {
content: "",
markup: tokens[idx].markup,
skip: [tokens[idx].markup]
});
tokens.splice(idx, 1, codeInlineOpen, text3, codeInlineClose);
}
// src/skeleton/hooks/image.ts
var import_token = __toESM(require("markdown-it/lib/token"));
function image2(parameters) {
var _a;
const tokens = parameters.tokens;
for (let i = 0; i < tokens.length; i++) {
const token2 = tokens[i];
if (token2.type !== "inline") {
continue;
}
const inlines = token2.children ?? [];
let j;
for (j = 0; j < inlines.length; j++) {
if (inlines[j].type === "image") {
const openToken = new import_token.default("image_open", "img", 0);
const closeToken = new import_token.default("image_close", "img", 0);
closeToken.attrs = inlines[j].attrs;
inlines.splice(j, 1, openToken, ...((_a = inlines[j]) == null ? void 0 : _a.children) ?? [], closeToken);
}
}
}
return "";
}
// src/skeleton/hooks/index.ts
var hooks2 = {
[import_markdown_it_custom_renderer3.CustomRendererLifeCycle.BeforeRender]: [image2, meta],
[import_markdown_it_custom_renderer3.CustomRendererLifeCycle.AfterInlineRender]: [afterInline2],
[import_markdown_it_custom_renderer3.CustomRendererLifeCycle.BeforeInlineRender]: [beforeInline]
};
function countStartIndexes(acc, line) {
acc.push(acc[acc.length - 1] + line.length + 1);
return acc;
}
function countEndIndexes(acc, line) {
acc.push((acc[acc.length - 1] || 0) + line.length + 1);
return acc;
}
function initState(source, md2) {
return {
md: md2,
source,
result: source,
lines: {
start: source.split("\n").reduce(countStartIndexes, [0]),
end: source.split("\n").reduce(countEndIndexes, [])
},
cursor: 0,
gap: 0,
segments: [],
hooks: {
before: {
_store: /* @__PURE__ */ new Map(),
add(token2, hook) {
const box = this._store.get(token2) || [];
box.push(hook);
this._store.set(token2, box);
},
get(token2) {
return this._store.get(token2) || [];
}
},
after: {
_store: /* @__PURE__ */ new Map(),
add(token2, hook) {
const box = this._store.get(token2) || [];
box.push(hook);
this._store.set(token2, box);
},
get(token2) {
return this._store.get(token2) || [];
}
}
},
skeleton: {
id: 1
}
};
}
// src/skeleton/rules/link.ts
var initState2 = () => ({
link: {
pending: [],
map: /* @__PURE__ */ new Map()
}
});
function isAutolink(token2) {
return token2.markup === "autolink";
}
function isRefLink(open, text3, close) {
if ((open == null ? void 0 : open.type) !== "link_open") {
return false;
}
if ((text3 == null ? void 0 : text3.type) !== "text") {
return false;
}
if ((close == null ? void 0 : close.type) !== "link_close") {
return false;
}
return (text3 == null ? void 0 : text3.content) === "{#T}";
}
var link2 = {
link_open: function(tokens, idx) {
const open = tokens[idx];
const text3 = tokens[idx + 1];
const close = tokens[idx + 2];
if (isAutolink(open)) {
const autolink = token("link_auto", {
content: "<" + tokens[idx + 1].content + ">"
});
tokens.splice(idx, 3, autolink);
return "";
}
if (isRefLink(open, text3, close)) {
open.reflink = true;
text3.reflink = true;
}
this.state.link.pending.push(open);
return "";
},
link_close: function(tokens, idx) {
const close = tokens[idx];
const open = this.state.link.pending.pop();
if ((open == null ? void 0 : open.type) !== "link_open") {
throw new Error("failed to render link token");
}
this.state.link.map.set(close, open);
const titleAttr = open.attrGet("title") || "";
close.skip = close.skip || [];
close.skip.push(")");
if (titleAttr) {
const consumer = new Consumer(titleAttr, 0, this.state);
const title = token("text", { content: titleAttr });
const parts = consumer.process(title);
open.attrSet("title", consumer.content);
this.state.hooks.before.add(close, (consumer2) => {
parts.forEach(({ part, past }) => consumer2.replace(part, past));
});
} else {
close.skip.unshift("(");
}
return "";
}
};
// src/skeleton/rules/image.ts
var image3 = {
image_close: function(tokens, idx) {
const close = tokens[idx];
const titleAttr = close.attrGet("title") || "";
const heightAttr = close.attrGet("height") || "";
const widthAttr = close.attrGet("width") || "";
close.skip = close.skip || [];
close.skip.push(widthAttr, heightAttr, ")");
if (titleAttr) {
const consumer = new Consumer(titleAttr, 0, this.state);
const title = token("text", { content: titleAttr });
const parts = consumer.process(title);
close.attrSet("title", consumer.content);
this.state.hooks.before.add(close, (consumer2) => {
parts.forEach(({ part, past }) => {
consumer2.replace(part, past);
});
});
} else {
close.skip.unshift("(");
}
return "";
}
};
// src/skeleton/rules/table.ts
function escapeMarkupLikeChars(tokens, idx) {
var _a;
const inline = tokens[idx + 1];
if (inline && ((_a = inline.children) == null ? void 0 : _a.length)) {
for (let i = 0; i < inline.children.length; i++) {
const token2 = inline.children[i];
if (token2.type === "text" || token2.type === "code_inline") {
token2.content = token2.content.replace(/\|/g, "\\|");
}
}
}
return "";
}
var table = {
th_open: escapeMarkupLikeChars,
td_open: escapeMarkupLikeChars
};
// src/skeleton/rules/text.ts
var text2 = {
text_special: (tokens, i) => {
tokens[i].type = "text";
tokens[i].content = tokens[i].markup;
return "";
},
text: function(tokens, i) {
const token2 = tokens[i];
if ((token2 == null ? void 0 : token2.generated) !== "liquid" && token2.content) {
const tokenizer = new Tokenizer(token2.content);
const liquidTokens = tokenizer.tokenize();
if (liquidTokens.length > 1 || liquidTokens[0].type !== "text") {
tokens.splice(i, 1, ...liquidTokens);
}
}
return "";
}
};
// src/skeleton/rules/blockquote.ts
var blockquote = {
blockquote_open: (tokens, idx) => {
var _a;
for (let i = idx + 1; i < tokens.length; i++) {
if (tokens[i].type === "blockquote_open" || tokens[i].type === "blockquote_close") {
return "";
}
if (tokens[i].type === "inline") {
const map = tokens[i].map;
const inlines = (_a = tokens[i].children || []) == null ? void 0 : _a.reduce(
(acc, token2) => {
acc[acc.length - 1].push(token2);
if (token2.type === "softbreak") {
acc.push([]);
}
return acc;
},
[[]]
).map(
(children, index) => token("inline", {
children,
map: map ? [map[0] + index, map[1]] : null
})
);
tokens.splice(i, 1, ...inlines);
}
}
return "";
}
};
// src/skeleton/rules/code.ts
var code = {
fence: function(tokens, idx) {
const code2 = tokens[idx];
if (!code2.info || ["bash", "sh", "shell"].includes(code2.info)) {
const rx = /<(.*?)>/g;
const parts = [];
let match;
while (match = rx.exec(code2.content)) {
parts.push(match[1]);
}
const consumer = new Consumer(this.state.result, this.state.cursor, this.state);
const texts = parts.map((part) => [
token("fake", { skip: "<" }),
token("text", { content: part }),
token("fake", { skip: ">" })
]);
consumer.window(code2.map, this.state.gap);
texts.forEach((text3) => consumer.consume(text3));
this.state.result = consumer.content;
this.state.cursor = consumer.cursor;
this.state.gap += consumer.gap;
}
return "";
}
};
// src/skeleton/rules/index.ts
var rules3 = {
...text2,
...link2,
...image3,
...table,
...blockquote,
...code
};
var initState3 = () => ({
...initState2()
});
// src/skeleton/index.ts
function render2(parameters) {
validateParams3(parameters);
const { markdown, lang } = parameters;
const md2 = new import_markdown_it5.default({ html: true });
const mdOptions = {
initState: () => ({
...initState(markdown, md2),
...initState3()
}),
rules: rules3,
hooks: hooks2
};
const diplodocOptions = {
lang: lang ?? "ru",
notesAutotitle: false,
path: ""
};
md2.disable("reference");
md2.disable("text_join");
md2.disable("entity");
md2.normalizeLink = (a) => a;
md2.normalizeLinkText = (a) => a;
md2.use(import_markdown_it_meta.default, diplodocOptions);
md2.use(includes_default, diplodocOptions);
md2.use(import_notes.default, diplodocOptions);
md2.use(import_cut.default, diplodocOptions);
md2.use(import_markdown_it_sup.default, diplodocOptions);
md2.use(import_checkbox.default, diplodocOptions);
md2.use(import_monospace.default, diplodocOptions);
md2.use(import_imsize.default, diplodocOptions);
md2.use(import_file2.default, diplodocOptions);
md2.use(import_video2.default, diplodocOptions);
md2.use(import_table2.default, diplodocOptions);
md2.use(import_markdown_it_custom_renderer4.customRenderer, mdOptions);
md2.render(markdown, {
source: markdown.split("\n")
});
const state2 = md2.renderer.state;
const xliff = XLF.generate(parameters, state2.segments);
return { skeleton: state2.result, xliff, units: state2.segments };
}
function validateParams3(parameters) {
const { markdown } = parameters;
(0, import_assert5.ok)(markdown !== void 0, "Markdown is empty");
}
// src/api/extract.ts
function extract(parameters) {
if (!parameters.markdown) {
return { xliff: "", units: [], skeleton: "" };
}
if (!parameters.markdownPath) {
parameters.markdownPath = "markdown.md";
}
if (!parameters.skeletonPath) {
parameters.skeletonPath = "markdown.skl.md";
}
return render2(parameters);
}
// src/api/compose.ts
var import_assert8 = require("assert");
// src/xliff/translations/index.ts
var import_assert7 = require("assert");
// src/xliff/renderer/xliff-md/index.ts
var import_assert6 = __toESM(require("assert"));
// src/xliff/token.ts
var xliffTagTokenNodeTypes = /* @__PURE__ */ new Set(["open", "close", "self-closing"]);
function isXLFTextToken(token2) {
return (token2 == null ? void 0 : token2.type) === "text";
}
function isXLFTagToken(token2) {
return (token2 == null ? void 0 : token2.type) === "tag" && xliffTagTokenNodeTypes.has(token2.nodeType);
}
// src/xliff/renderer/xliff-md/index.ts
var literal = (token2) => {
(0, import_assert6.default)(isXLFTagToken(token2));
token2;
const { equivText } = token2;
(0, import_assert6.default)(
equivText == null ? void 0 : equivText.length,
"literal tag should contain original markup inside equiv-text attritbute"
);
return equivText;
};
var openclose = (token2) => {
(0, import_assert6.default)(isXLFTagToken(token2));
token2;
if (token2.nodeType === "open") {
return token2.begin;
} else {
return token2.end;
}
};
var spaced = (actor) => (token2) => " " + actor(token2);
var quoted = (actor) => (token2) => '"' + actor(token2) + '"';
var attr = spaced(quoted(literal));
var XLFMDRenderer = class {
rules;
constructor() {
this.rules = {
text: this.text.bind(this),
tag: this.tag.bind(this),
x: this.x.bind(this),
g: this.g.bind(this),
// simple pair syntax
// strong
strong_open: literal,
strong_close: literal,
// em
em_open: literal,
em_close: literal,
// s
s_open: literal,
s_close: literal,
// sup
sup_open: literal,
sup_close: literal,
// samp
samp_open: literal,
samp_close: literal,
// code