UNPKG

usfm-editor

Version:
206 lines (157 loc) 6.26 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.slateToUsfm = slateToUsfm; require("core-js/modules/es.array.reduce.js"); var _NodeTypes = _interopRequireDefault(require("../utils/NodeTypes")); var _MyText = require("../plugins/helpers/MyText"); var _slate = require("slate"); var _UsfmMarkers = require("../utils/UsfmMarkers"); var _MarkerInfoMap = _interopRequireDefault(require("../utils/MarkerInfoMap")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function slateToUsfm(value) { const usfm = serializeRecursive(value); const normalized = normalizeWhitespace(usfm); // Convert the workaround for the pipe literal back to the pipe character return normalized.replace(/&pipe;/g, "|"); } function normalizeWhitespace(usfm) { // Remove leading newline usfm = usfm.replace(/^\n/, ""); // Any whitespace ending in a newline should just be a newline usfm = usfm.replace(/(\s+?)\n/g, "\n"); // Remove space at the end of the document usfm = usfm.replace(/(\s+)$/, ""); return usfm; } function isTypedElement(node) { return _slate.Element.isElement(node) && node.type !== undefined; } function serializeRecursive(value) { if (Array.isArray(value)) { return value.map(serializeRecursive).reduce(concatUsfm); } else if (_slate.Element.isElement(value) && isTypedElement(value)) { // value implements the "Element" interface if (value.type === _UsfmMarkers.UsfmMarkers.CHAPTERS_AND_VERSES.v) { return serializeVerseNumber(value); } else if (isChapterHeaderOrVerse(value)) { // Structural types (header, chapter, verse) do not // have a tag that needs to be converted to usfm return serializeRecursive(value.children); } else { return serializeElement(value); } } else if (_slate.Text.isText(value)) { return serializeTexts([value]); } else { return ""; } } function isChapterHeaderOrVerse(h) { return [_NodeTypes.default.CHAPTER, _NodeTypes.default.HEADERS, _NodeTypes.default.VERSE].includes(h.type); } function concatUsfm(a, b) { return a.concat(b); } function serializeVerseNumber(verseNumber) { var _verseNumber$children; const verseNumberText = (_verseNumber$children = verseNumber.children[0]) === null || _verseNumber$children === void 0 ? void 0 : _verseNumber$children.text; // Front matter must start on the next line. // Whitespace will be normalized later, so empty front // matter will not cause multiple newlines. return verseNumberText === "front" ? "\n" : `\n\\v ${verseNumberText} `; } function serializeElement(value) { const marker = serializeMarker(value.type); const content = serializeTexts(value.children); const endMarker = getEndMarker(value.type); return marker + content + endMarker; } function serializeMarker(type) { if (type === _NodeTypes.default.INLINE_CONTAINER) { return ""; } return `\n\\${type} `; } function getEndMarker(type) { const tagInfo = _MarkerInfoMap.default.get(type); return tagInfo && tagInfo.endMarker ? `\\${tagInfo.endMarker}` : ""; } /** * Processes marks to construct the corresponding usfm content */ function serializeTexts(children) { let usfm = ""; let markStack = new Array(); for (const text of children) { if (!_MyText.MyText.isText(text)) { console.error("Unexpected non-text element in serializeTexts!"); continue; } const marks = _MyText.MyText.marks(text); // If this child lacks marks that were on the previous ones, close them const removedMarks = setDiff(markStack, marks); let result = closeMarks(usfm, markStack, removedMarks); usfm = result.usfm; markStack = result.stack; // If this child has marks that weren't on the previous ones, open them const addedMarks = setDiff(marks, markStack); const markerWithNoEndMarker = addedMarks.find(m => { var _MarkerInfoMap$get; return ((_MarkerInfoMap$get = _MarkerInfoMap.default.get(m)) === null || _MarkerInfoMap$get === void 0 ? void 0 : _MarkerInfoMap$get.endMarker) === undefined; }); // Sometimes an empty text and an adjacent text will have the same marker. // Forcing normalization would fix this, but for now we need to ensure that // the text field is non-empty. if (markerWithNoEndMarker && text.text != "") { usfm += `\\${markerWithNoEndMarker} `; } const closeableAddedMarks = addedMarks.filter(m => m != markerWithNoEndMarker); result = openMarks(usfm, markStack, closeableAddedMarks); usfm = result.usfm; markStack = result.stack; usfm += text.text; } const res = closeMarks(usfm, markStack, [...markStack]); return res.usfm; } function setDiff(a, b) { const setA = new Set(a); const setB = new Set(b); return [...setA].filter(x => !setB.has(x)); } /** * Adds closing tags for marks that should be closed (e.g. \nd or \+nd) */ function closeMarks(usfm, markStack, toClose) { while (toClose.length > 0) { const mark = toClose[0]; let closing = undefined; while (closing != mark) { var _MarkerInfoMap$get2; closing = markStack.pop(); if (!closing) { console.error("Internal state error closing markers!"); // This is bad, but continue by closing what we can closing = mark; } const endMarker = (_MarkerInfoMap$get2 = _MarkerInfoMap.default.get(closing)) === null || _MarkerInfoMap$get2 === void 0 ? void 0 : _MarkerInfoMap$get2.endMarker; // If there are still marks in the stack, // this output tag should be nested, so add a "+" const plus = markStack.length > 0 ? "+" : ""; usfm += `\\${plus}${endMarker}`; toClose = toClose.filter(x => x != closing); // Remove from list } } return { usfm: usfm, stack: markStack }; } /** * Adds opening tags for marks that should be opened (e.g. \nd or \+nd) */ function openMarks(usfm, markStack, toOpen) { for (const mark of toOpen) { markStack.push(mark); // If there are additional marks in the stack, // this output tag should be nested, so add a "+" const plus = markStack.length > 1 ? "+" : ""; usfm += `\\${plus}${mark} `; } return { usfm: usfm, stack: markStack }; }