UNPKG

devexpress-richedit

Version:

DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.

309 lines (308 loc) 16.6 kB
import { UnitConverter } from '@devexpress/utils/lib/class/unit-converter'; import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed'; import { ListUtils } from '@devexpress/utils/lib/utils/list'; import { MathUtils } from '@devexpress/utils/lib/utils/math'; import { TabLeaderType } from '../../../layout/main-structures/layout-boxes/layout-tab-space-box'; import { MaskedParagraphPropertiesBundleFull } from '../../../rich-utils/properties-bundle'; import { ConstBookmark } from '../../bookmarks'; import { CreateBookmarkHistoryItem } from '../../history/items/bookmark-history-items'; import { FieldInsertHistoryItem } from '../../history/items/field-insert-history-item'; import { DeleteTabAtParagraphHistoryItem, InsertTabToParagraphHistoryItem } from '../../history/items/paragraph-properties-history-items'; import { InsertParagraphManipulatorParams } from '../../manipulators/paragraph-manipulator/insert-paragraph-manipulator-params'; import { TabAlign } from '../../paragraph/paragraph'; import { ParagraphStyle, TabInfo } from '../../paragraph/paragraph-style'; import { RichUtils } from '../../rich-utils'; import { SubDocumentInterval, SubDocumentIntervals, SubDocumentPosition } from '../../sub-document'; import { Field } from '../field'; import { TocFieldRequestManager } from '../field-request-manager'; import { FieldName } from '../names'; import { FieldsWaitingForUpdate, UpdateFieldsOptions } from '../tree-creator'; import { FieldSwitchType } from './field-code-parser'; import { FieldCodeParserClientUpdatingBase } from './field-code-parser-client-updating-base'; import { ModelIterator } from '../../model-iterator'; export class FieldCodeParserToc extends FieldCodeParserClientUpdatingBase { get name() { return FieldName.Toc; } fillResult() { this.modelManager.history.beginTransaction(); const params = this.getTocParserParams(); const tocElements = this.createTocElements(params); this.setInputPositionState(); this.resetParagraphTabs(); if (tocElements.length > 0) this.createToc(tocElements, params); else this.insertNoTocText(); this.modelManager.history.endTransaction(); return true; } createTocElements(params) { if (params.fromTc) return this.createTocElementsByTc(params); if (params.fromSeq) return this.createTocElementsBySeq(params); return this.createTocElementsByOutlineLevel(params); } createTocElementsByTc(params) { let tocElements = []; for (let i = 0, field; field = this.subDocument.fields[i]; i++) if (field.isTcField()) { const tcInfo = field.getTcInfo(); if (tcInfo.identifier.toUpperCase() == params.tcIdentifier.toUpperCase() && (!params.useSpecifiedTcLevels || (tcInfo.level >= params.tcLevelStart && tcInfo.level <= params.tcLevelEnd))) { const bookmarkName = this.getBookmarkName(field.getAllFieldInterval()); if (this.isValidHeading(tcInfo.text)) tocElements.push(new TocElement(bookmarkName, tcInfo.text, tcInfo.level, false)); } } return tocElements; } createTocElementsBySeq(params) { let tocElements = []; for (let i = 0, field; field = this.subDocument.fields[i]; i++) if (field.isSequenceField()) { const seqInfo = field.getSequenceInfo(); if (seqInfo.identifier.toUpperCase() == params.seqIdentifier.toUpperCase()) { const paragraph = this.subDocument.getParagraphByPosition(field.getCodeStartPosition()); const paragraphInterval = paragraph.interval; paragraphInterval.length -= 1; const text = this.subDocument.getSimpleText(paragraphInterval).trim(); const bookmarkName = this.getBookmarkName(paragraphInterval); if (this.isValidHeading(text)) tocElements.push(new TocElement(bookmarkName, text, 1, false)); } } return tocElements; } createTocElementsByOutlineLevel(params) { let tocElements = []; for (let i = 0, paragraph; paragraph = this.subDocument.paragraphs[i]; i++) { const styleOutlineLevel = paragraph.paragraphStyle.getMergedParagraphProperties().outlineLevel; const level = params.fromOutlineLevel && styleOutlineLevel <= 0 ? paragraph.getParagraphMergedProperties().outlineLevel : styleOutlineLevel; if (level > 0 && (!params.useSpecifiedLevels || (level >= params.specifiedLevelStart && level <= params.specifiedLevelEnd))) { const paragraphInterval = this.getParagraphInterval(paragraph); const bookmarkName = this.getBookmarkName(paragraphInterval); const mainText = this.subDocument.getSimpleText(paragraph.interval).trim() .replace(new RegExp(RichUtils.specialCharacters.TabMark, "gi"), RichUtils.specialCharacters.Space); let text = mainText; const hasNumbering = paragraph.getNumberingListIndex() >= 0; if (hasNumbering) text = paragraph.getNumberingListText() + paragraph.getNumberingListSeparatorChar() + mainText; if (this.isValidHeading(text)) tocElements.push(new TocElement(bookmarkName, text, level, hasNumbering)); } } return tocElements; } static getParagraphInterval(subDocument, paragraph) { const characterIterator = new ModelIterator(subDocument, false); const paragraphMarkPosition = paragraph.interval.end - 1; characterIterator.setPosition(paragraph.startLogPosition.value); while (characterIterator.getAbsolutePosition() < paragraphMarkPosition && this.isWhiteSpace(characterIterator.getCurrentChar())) { if (!characterIterator.moveToNextChar()) break; } const intervalStart = characterIterator.getAbsolutePosition(); const length = paragraph.interval.length - (intervalStart - paragraph.startLogPosition.value) - 1; return new FixedInterval(intervalStart, length); } getParagraphInterval(paragraph) { return FieldCodeParserToc.getParagraphInterval(this.subDocument, paragraph); } static isWhiteSpace(ch) { return RichUtils.isWhitespace.test(ch) || ch == RichUtils.specialCharacters.NonBreakingSpace || ch == RichUtils.specialCharacters.PageBreak || ch == RichUtils.specialCharacters.ColumnBreak || ch == RichUtils.specialCharacters.LineBreak; } createToc(tocElements, params) { let fieldsForUpdate = []; for (let i = 0, tocElement; tocElement = tocElements[i]; i++) { const startPos = this.interval.start; this.resetParagraphTabs(); this.insertHeading(tocElement); if (tocElement.hasNumbering) this.insertTocNumberingTabInfo(tocElement.level); if (!params.useCustomSeparator) this.insertDotTabInfo(); if (!params.omitsPageNumbers || tocElement.level < params.omitsPageNumbersStart || tocElement.level > params.omitsPageNumbersEnd) { this.insertSeparator(params); fieldsForUpdate.push(this.insetPageRef(tocElement)); } if (params.asHyperlink) this.createLocalHyperLink(FixedInterval.fromPositions(startPos, this.interval.end), tocElement.bookmarkName); this.setParagraphStyle(tocElement); if (tocElements[i + 1]) { this.removeInterval(this.interval); this.modelManager.modelManipulator.paragraph.insertParagraphViaHistory(new InsertParagraphManipulatorParams(new SubDocumentPosition(this.subDocument, this.interval.start), this.inputPos.charPropsBundle, MaskedParagraphPropertiesBundleFull.notSetted, true, () => { })); } } if (fieldsForUpdate.length > 0) { let intervalsForUpdate = []; ListUtils.forEach(fieldsForUpdate, (field) => { intervalsForUpdate.push(field.getAllFieldInterval()); }); new FieldsWaitingForUpdate(this.modelManager, this.layoutFormatterManager, new TocFieldRequestManager(), new SubDocumentIntervals(this.subDocument, intervalsForUpdate), new UpdateFieldsOptions(), () => { }).update(null); } } insertNoTocText() { this.replaceTextByInterval(this.interval, this.layoutFormatterManager.stringResources.commonLabels.noTocEntriesFound); } resetParagraphTabs() { const paragraphIndex = this.subDocument.getParagraphIndexByPosition(this.interval.start); const paragraph = this.subDocument.paragraphs[paragraphIndex]; let tabs = paragraph.getTabs(); let tab; while (tab = tabs.positions.pop()) this.modelManager.history.addAndRedo(new DeleteTabAtParagraphHistoryItem(this.modelManager.modelManipulator, new SubDocumentInterval(this.subDocument, paragraph.interval), tab)); } setParagraphStyle(tocElement) { const paragraph = this.subDocument.paragraphs[this.subDocument.getParagraphIndexByPosition(this.interval.start)]; this.modelManager.modelManipulator.style.applyParagraphStyleByName(new SubDocumentInterval(this.subDocument, paragraph.interval), `${ParagraphStyle.tocStyleName} ${tocElement.level}`); } insertHeading(tocElement) { this.replaceTextByInterval(this.interval, tocElement.text); } insertSeparator(params) { const separator = params.useCustomSeparator ? params.separator : RichUtils.specialCharacters.TabMark; this.replaceTextByInterval(this.interval, separator); } insertDotTabInfo() { const sectionProperties = this.modelManager.model.getSectionByPosition(this.interval.start).sectionProperties; const tabPosition = sectionProperties.pageWidth - sectionProperties.marginLeft - sectionProperties.marginRight; this.insertTabInfo(tabPosition, TabLeaderType.Dots, TabAlign.Right, false); } insertTocNumberingTabInfo(level) { const tocTabWidth = UnitConverter.inchesToTwips(0.31) + (level - 1) * UnitConverter.inchesToTwips(0.15); this.insertTabInfo(tocTabWidth, TabLeaderType.None, TabAlign.Left, false); } insertTabInfo(tabPosition, tabLeaderType, tabAlign, isDefault) { const interval = this.interval; const tabInfo = new TabInfo(tabPosition, tabAlign, tabLeaderType, false, isDefault); this.modelManager.history.addAndRedo(new InsertTabToParagraphHistoryItem(this.modelManager.modelManipulator, new SubDocumentInterval(this.subDocument, interval), tabInfo)); } insetPageRef(tocElement) { const interval = this.interval; this.modelManager.history.addAndRedo(new FieldInsertHistoryItem(this.modelManager.modelManipulator, this.subDocument, interval.start, 0, interval.length, false, this.inputPos.charPropsBundle)); let fieldIndex = Field.normedBinaryIndexOf(this.subDocument.fields, interval.start + 1); let field = this.subDocument.fields[fieldIndex]; const code = this.modelManager.model.simpleFormattersManager.formatString("PAGEREF \"{0}\" \\h", tocElement.bookmarkName); this.replaceTextByInterval(field.getCodeInterval(), code); return field; } getTocParserParams() { let params = new TocParserParameters(); for (let i = 0, switchInfo; switchInfo = this.switchInfoList[i]; i++) if (switchInfo.type == FieldSwitchType.FieldSpecific) { switch (switchInfo.name.toLocaleUpperCase()) { case "H": params.asHyperlink = true; break; case "F": params.tcIdentifier = switchInfo.arg; params.fromTc = !!params.tcIdentifier; break; case "C": params.seqIdentifier = switchInfo.arg; params.fromSeq = !!params.seqIdentifier; break; case "U": params.fromOutlineLevel = true; break; case "P": params.useCustomSeparator = true; params.separator = switchInfo.arg; break; case "O": const oArg = new RangedArg(switchInfo.arg); params.useSpecifiedLevels = oArg.isValid(); params.specifiedLevelStart = oArg.getStart(); params.specifiedLevelEnd = oArg.getEnd(); break; case "L": const lArg = new RangedArg(switchInfo.arg); params.useSpecifiedTcLevels = lArg.isValid(); params.tcLevelStart = lArg.getStart(); params.tcLevelEnd = lArg.getEnd(); break; case "N": params.omitsPageNumbers = true; const nArg = new RangedArg(switchInfo.arg); if (nArg.isValid()) { params.omitsPageNumbersStart = nArg.getStart(); params.omitsPageNumbersEnd = nArg.getEnd(); } else { params.omitsPageNumbersStart = 1; params.omitsPageNumbersEnd = 9; } break; } } return params; } isValidHeading(text) { if (!text) return false; for (let i = 0, char; char = text[i]; i++) if (!RichUtils.isWhitespace.test(char)) return true; return false; } get interval() { let pos = this.getTopField().getResultInterval().end; return new FixedInterval(pos, 0); } getBookmarkName(interval) { const bookmark = ListUtils.elementBy(this.subDocument.bookmarks, (bm) => bm.isToc() && bm.start == interval.start && bm.end == interval.end); return bookmark ? bookmark.name : this.createNewBookmark(interval); } createNewBookmark(interval) { const name = this.generateNewBookmarkName(); this.modelManager.history.addAndRedo(new CreateBookmarkHistoryItem(this.modelManager.modelManipulator, this.subDocument, new ConstBookmark(interval, name))); return name; } generateNewBookmarkName() { while (true) { const name = this.modelManager.model.simpleFormattersManager.formatString("_Toc{0}", MathUtils.getRandomInt(0, 10000000000)); for (let i = 0, bookmark; bookmark = this.subDocument.bookmarks[i]; i++) if (bookmark.name.toUpperCase() == name.toUpperCase()) continue; return name; } } } export class TocParserParameters { constructor() { this.asHyperlink = false; this.fromOutlineLevel = false; this.fromTc = false; this.fromSeq = false; this.useCustomSeparator = false; this.useSpecifiedLevels = false; this.omitsPageNumbers = false; this.useSpecifiedTcLevels = false; } } export class TocElement { constructor(bookmarkName, text, level, hasNumbering) { this.bookmarkName = bookmarkName; this.text = text; this.level = level; this.hasNumbering = hasNumbering; } } export class RangedArg { constructor(arg) { this.start = Number(arg.split('-')[0]); this.end = Number(arg.split('-')[1]); } getStart() { return this.isValid() ? this.start : null; } getEnd() { return this.isValid() ? this.end : null; } isValid() { return !isNaN(this.start) && !isNaN(this.end) && this.start >= 0 && this.end >= 0 && this.end >= this.start; } }