UNPKG

@diplodoc/markdown-translation

Version:
1,690 lines (1,637 loc) 54.5 kB
"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 = "!["; const end = "](" + [ src && decodeURL(src), 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