devexpress-richedit
Version: 
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
448 lines (447 loc) • 21.4 kB
JavaScript
import { Errors } from '@devexpress/utils/lib/errors';
import { DateTimeFieldFormatter } from '@devexpress/utils/lib/formatters/date-time-field';
import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
import { isDefined } from '@devexpress/utils/lib/utils/common';
import { StringUtils } from '@devexpress/utils/lib/utils/string';
import { InputPositionBase } from '../../../selection/input-position-base';
import { SelectionIntervalsInfo } from '../../../selection/selection-intervals-info';
import { InsertLayoutDependentTextManipulatorParams, InsertTextManipulatorParams } from '../../manipulators/text-manipulator/insert-text-manipulator-params';
import { NumberConverterCreator } from '../../number-converters/number-converter-creator';
import { RichUtils } from '../../rich-utils';
import { RunType } from '../../runs/run-type';
import { SubDocumentInterval, SubDocumentPosition } from '../../sub-document';
import { Field } from '../field';
import { FieldsWaitingForUpdate } from '../tree-creator';
export var FieldCodeParserState;
(function (FieldCodeParserState) {
    FieldCodeParserState[FieldCodeParserState["start"] = 0] = "start";
    FieldCodeParserState[FieldCodeParserState["addedParsersCodePart"] = 1] = "addedParsersCodePart";
    FieldCodeParserState[FieldCodeParserState["updatedParsersCodePart"] = 2] = "updatedParsersCodePart";
    FieldCodeParserState[FieldCodeParserState["resultPartCreated"] = 3] = "resultPartCreated";
    FieldCodeParserState[FieldCodeParserState["addedParsersResultPart"] = 4] = "addedParsersResultPart";
    FieldCodeParserState[FieldCodeParserState["end"] = 5] = "end";
})(FieldCodeParserState || (FieldCodeParserState = {}));
export var FieldSwitchType;
(function (FieldSwitchType) {
    FieldSwitchType[FieldSwitchType["Error"] = 0] = "Error";
    FieldSwitchType[FieldSwitchType["DateAndTime"] = 1] = "DateAndTime";
    FieldSwitchType[FieldSwitchType["Numeric"] = 2] = "Numeric";
    FieldSwitchType[FieldSwitchType["General"] = 3] = "General";
    FieldSwitchType[FieldSwitchType["FieldSpecific"] = 4] = "FieldSpecific";
})(FieldSwitchType || (FieldSwitchType = {}));
export var FieldMailMergeType;
(function (FieldMailMergeType) {
    FieldMailMergeType[FieldMailMergeType["NonMailMerge"] = 1] = "NonMailMerge";
    FieldMailMergeType[FieldMailMergeType["MailMerge"] = 2] = "MailMerge";
    FieldMailMergeType[FieldMailMergeType["Mixed"] = 3] = "Mixed";
})(FieldMailMergeType || (FieldMailMergeType = {}));
export class FieldSwitch {
    constructor(type, name, arg) {
        this.name = name;
        this.type = type;
        this.arg = arg;
    }
}
export class FieldParameter {
    constructor(interval, textRepresentation, quoted = false) {
        this.text = textRepresentation;
        this.interval = interval;
        this.quoted = quoted;
    }
    clone() {
        return new FieldParameter(this.interval.clone(), this.text, this.quoted);
    }
}
export class FieldCodeParserHelper {
    static isWhitespaceAndTextRunType(char, type) {
        return RichUtils.isWhitespace.test(char) && type == RunType.TextRun;
    }
    static isBackslesh(char) {
        return char == "\\";
    }
    static isQuote(char) {
        return char == "\"";
    }
}
export class FieldCodeParser {
    constructor(args) {
        this.switchInfoList = [];
        this.parameterInfoList = [];
        this.modelManager = args.modelManager;
        this.layoutFormatterManager = args.layoutFormatterManager;
        this.requestManager = args.requestManager;
        this.subDocument = args.subDocument;
        this.inputPos = new InputPositionBase().setIntervals(SelectionIntervalsInfo.fromPosition(args.subDocument, args.field.getResultInterval().start));
        this.fieldsStack = [args.field];
        this.modelIterator = args.modelIterator;
        this.lowLevelParsers = [];
        this.parserState = FieldCodeParserState.start;
        this.fieldNameFirstLetterPosition = args.subDocument.positionManager.registerPosition(args.fieldNameFirstLetterPosition);
    }
    removeInterval(interval) {
        this.modelManager.modelManipulator.range.removeInterval(new SubDocumentInterval(this.subDocument, interval), true, false);
    }
    replaceTextByInterval(interval, text) {
        this.removeInterval(interval);
        this.modelManager.modelManipulator.text.insertTextViaHistory(new InsertTextManipulatorParams(new SubDocumentPosition(this.subDocument, interval.start), this.inputPos.charPropsBundle, RunType.TextRun, text));
    }
    replaceTextByLayoutDependentRun(interval) {
        this.removeInterval(interval);
        this.modelManager.modelManipulator.text.insertTextViaHistory(new InsertLayoutDependentTextManipulatorParams(new SubDocumentPosition(this.subDocument, interval.start), this.inputPos.charPropsBundle));
    }
    static finalAction(layoutFormatterManager, field, subDocument) {
        field.showCode = false;
        if (layoutFormatterManager)
            layoutFormatterManager.invalidator.onIntervalChanged(subDocument.id, field.getAllFieldInterval());
    }
    destructor() {
        this.subDocument.positionManager.unregisterPosition(this.fieldNameFirstLetterPosition);
        this.fieldNameFirstLetterPosition = null;
        FieldCodeParser.finalAction(this.layoutFormatterManager, this.getTopField(), this.subDocument);
    }
    getMailMergeType() {
        throw new Error(Errors.NotImplemented);
    }
    handleSwitch(newSwitch) {
        this.switchInfoList.push(newSwitch);
        return true;
    }
    handleParameter(newParameter) {
        this.parameterInfoList.push(newParameter);
        return true;
    }
    getFormattedResult(value) {
        if (!isDefined(value))
            return null;
        const manager = this.modelManager;
        let type = FieldSwitchType.Error;
        let arg = "";
        for (let i = 0, switchInfo; switchInfo = this.switchInfoList[i]; i++) {
            if (switchInfo.type != FieldSwitchType.FieldSpecific) {
                arg = switchInfo.arg;
                type = switchInfo.type;
                break;
            }
        }
        const numericalValue = typeof value == 'string' ? parseInt(value) : value;
        if (type == FieldSwitchType.General && !StringUtils.isNullOrEmpty(arg)) {
            if (!isNaN(numericalValue))
                return NumberConverterCreator.createConverterByTypeName(arg, manager.model.simpleFormattersManager).convertNumber(numericalValue);
            if (typeof value == 'string')
                switch (arg.toLowerCase()) {
                    case "upper":
                        return value.toLocaleUpperCase();
                    case 'lower':
                        return value.toLocaleLowerCase();
                    case 'firstcap':
                        return this.capitalizesFirstLetterOfFirstWord(value);
                    case 'caps':
                        return this.capitalizesFirstLetterOfEachWord(value);
                    default:
                        return value;
                }
        }
        else if (type == FieldSwitchType.DateAndTime) {
            const date = isNaN(numericalValue) ? new Date(value.toString()) : new Date(numericalValue);
            if (!isNaN(date.getDate())) {
                const dateFormatter = new DateTimeFieldFormatter(manager.richOptions.cultureOpts);
                return !StringUtils.isNullOrEmpty(arg) ? dateFormatter.format(date, arg) :
                    dateFormatter.format(date, manager.richOptions.fields.defaultDateFormat);
            }
        }
        else if (type == FieldSwitchType.Numeric && !isNaN(numericalValue) && !StringUtils.isNullOrEmpty(arg))
            return manager.model.simpleFormattersManager.formatNumber(arg, numericalValue);
        return value.toString();
    }
    capitalizesFirstLetterOfFirstWord(value) {
        let result = value;
        if (result.length > 0)
            result = this.replaceCharacter(result, 0, result[0].toLocaleUpperCase());
        return result;
    }
    capitalizesFirstLetterOfEachWord(value) {
        let result = value.toLocaleLowerCase();
        let index = this.skipCharacters(0, result, (str) => !this.isLetterOrDigit(str));
        while (index < result.length) {
            result = this.replaceCharacter(result, index, result[index].toLocaleUpperCase());
            index = this.skipCharacters(index, result, (str) => this.isLetterOrDigit(str));
            index = this.skipCharacters(index, result, (str) => !this.isLetterOrDigit(str));
        }
        return result;
    }
    replaceCharacter(source, index, chForReplace) {
        return source.substr(0, index) + chForReplace + source.substr(index + 1);
    }
    skipCharacters(index, str, predicate) {
        while (index < str.length && predicate(str[index]))
            index++;
        return index;
    }
    isLetterOrDigit(str) {
        if (str.length != 1)
            return false;
        if (str.match(/[0-9]/i))
            return true;
        if (str.toLocaleLowerCase() != str.toLocaleUpperCase())
            return true;
        return false;
    }
    setInputPositionState() {
        this.inputPos.setIntervals(SelectionIntervalsInfo.fromPosition(this.subDocument, this.fieldNameFirstLetterPosition.value));
    }
    getTopField() {
        return this.fieldsStack[0];
    }
    update(responce) {
        if (this.parserState == FieldCodeParserState.end)
            throw new Error("Excess call updated field");
        switch (this.parserState) {
            case FieldCodeParserState.start:
                var field = this.getTopField();
                if (this.collectAndUpdateLowLevelFields(field.index + 1, field.getCodeStartPosition(), field.getSeparatorPosition())) {
                    this.parserState = FieldCodeParserState.updatedParsersCodePart;
                    return this.parseCodeCurrentField(null);
                }
                this.parserState = FieldCodeParserState.addedParsersCodePart;
                return false;
            case FieldCodeParserState.addedParsersCodePart:
                if (this.updateLowLevelFields(responce)) {
                    this.parserState = FieldCodeParserState.updatedParsersCodePart;
                    return this.parseCodeCurrentField(responce);
                }
                return false;
            case FieldCodeParserState.updatedParsersCodePart:
                return this.parseCodeCurrentField(responce);
            case FieldCodeParserState.addedParsersResultPart:
                return this.updateFieldsInResult(responce);
        }
    }
    collectAndUpdateLowLevelFields(fieldIndex, startPosition, endPosition) {
        var fields = this.subDocument.fields;
        for (var field; field = fields[fieldIndex]; fieldIndex++) {
            if (field.getFieldStartPosition() > endPosition)
                break;
            if (field.parent != this.getTopField() || field.getFieldEndPosition() <= startPosition) {
                fieldIndex++;
                continue;
            }
            var fieldParser = FieldsWaitingForUpdate.getParser(this.modelManager, this.layoutFormatterManager, this.requestManager, this.subDocument, field);
            if (fieldParser) {
                if (!fieldParser.update(null))
                    this.lowLevelParsers.push(fieldParser);
                else
                    fieldParser.destructor();
            }
            else
                this.removeInterval(field.getResultInterval());
        }
        return this.lowLevelParsers.length == 0;
    }
    updateLowLevelFields(responce) {
        for (var parserIndex = 0, parser; parser = this.lowLevelParsers[parserIndex]; parserIndex++) {
            if (parser.update(responce)) {
                parser.destructor();
                this.lowLevelParsers.splice(parserIndex, 1);
                parserIndex--;
            }
        }
        return this.lowLevelParsers.length == 0;
    }
    parseCodeCurrentField(responce) {
        if (this.parseCodeCurrentFieldInternal(responce)) {
            switch (this.parserState) {
                case FieldCodeParserState.resultPartCreated:
                    return this.updateFieldsInResult(responce);
                case FieldCodeParserState.end:
                    return true;
                default:
                    throw new Error("wrong way");
            }
        }
        else {
            if (this.parserState == FieldCodeParserState.updatedParsersCodePart)
                return false;
            else
                throw new Error("wrong way");
        }
    }
    updateFieldsInResult(responce) {
        switch (this.parserState) {
            case FieldCodeParserState.resultPartCreated:
                var fieldIndex = Field.normedBinaryIndexOf(this.subDocument.fields, this.getTopField().getResultStartPosition() + 1);
                var field = this.getTopField();
                if (this.collectAndUpdateLowLevelFields(fieldIndex, field.getResultStartPosition(), field.getResultEndPosition())) {
                    this.parserState = FieldCodeParserState.end;
                    return true;
                }
                this.parserState = FieldCodeParserState.addedParsersResultPart;
                return false;
            case FieldCodeParserState.addedParsersResultPart:
                if (this.updateLowLevelFields(responce)) {
                    this.parserState = FieldCodeParserState.end;
                    return true;
                }
                return false;
        }
    }
    moveIteratorToNextChar() {
        if (this.modelIterator.run.getType() == RunType.FieldCodeEndRun)
            return false;
        if (!this.modelIterator.moveToNextChar())
            throw new Error("wrong way");
        while (true) {
            switch (this.modelIterator.run.getType()) {
                case RunType.FieldCodeStartRun:
                    var fieldIndex = Field.normedBinaryIndexOf(this.subDocument.fields, this.modelIterator.getAbsolutePosition() + 1);
                    var lowLevelField = this.subDocument.fields[fieldIndex];
                    this.fieldsStack.push(lowLevelField);
                    this.modelIterator.setPosition(lowLevelField.getResultStartPosition());
                    break;
                case RunType.FieldResultEndRun:
                case RunType.FieldCodeEndRun:
                    var lowLevelField = this.fieldsStack.pop();
                    if (this.fieldsStack.length == 0) {
                        this.fieldsStack.push(lowLevelField);
                        return false;
                    }
                    this.modelIterator.setPosition(lowLevelField.getFieldEndPosition());
                    break;
                default:
                    return true;
            }
        }
    }
    updateInfo() {
        if (!this.needUpdateInfo())
            return false;
        this.parseSwitchesAndArgs();
        this.updateInfoCore();
        return true;
    }
    needUpdateInfo() {
        return false;
    }
    updateInfoCore() { }
    parseSwitchesAndArgs() {
        while (this.skipWhitespaces() && this.modelIterator.run.getType() != RunType.FieldCodeEndRun) {
            var currChar = this.modelIterator.getCurrentChar();
            if (FieldCodeParserHelper.isBackslesh(currChar)) {
                var switchInfo = this.getSwitchInfo();
                if (switchInfo.type == FieldSwitchType.Error || !this.handleSwitch(switchInfo))
                    return false;
            }
            else {
                var paramInfo = this.getFieldParameterInfo();
                if (!paramInfo || !this.handleParameter(paramInfo))
                    return false;
            }
        }
        return this.modelIterator.run.getType() == RunType.FieldCodeEndRun;
    }
    skipWhitespaces() {
        var isFindWhitespace = false;
        do {
            if (FieldCodeParserHelper.isWhitespaceAndTextRunType(this.modelIterator.getCurrentChar(), this.modelIterator.run.getType()) ||
                this.modelIterator.run.getType() == RunType.ParagraphRun ||
                this.modelIterator.run.getType() == RunType.SectionRun)
                isFindWhitespace = true;
            else
                break;
        } while (this.moveIteratorToNextChar());
        return isFindWhitespace;
    }
    getFieldParameterInfo() {
        var startPosition = this.modelIterator.getAbsolutePosition();
        var parseResult = this.parseSwitchOrFieldArgument();
        if (!parseResult)
            return null;
        var argInterval = parseResult.quoted ?
            FixedInterval.fromPositions(startPosition + 1, this.modelIterator.getAbsolutePosition() - 1) :
            FixedInterval.fromPositions(startPosition, this.modelIterator.getAbsolutePosition());
        return new FieldParameter(argInterval, parseResult.argListChars.join(""), parseResult.quoted);
    }
    getSwitchInfo() {
        if (!this.moveIteratorToNextChar() || this.modelIterator.run.getType() != RunType.TextRun)
            return new FieldSwitch(FieldSwitchType.Error, "", "");
        var currChar = this.modelIterator.getCurrentChar();
        switch (currChar) {
            case "*": return this.makeSwitchInfo(FieldSwitchType.General, currChar, true);
            case "@": return this.makeSwitchInfo(FieldSwitchType.DateAndTime, currChar, true);
            case "#": return this.makeSwitchInfo(FieldSwitchType.Numeric, currChar, true);
            default:
                if (currChar == "!")
                    return this.makeSwitchInfo(FieldSwitchType.FieldSpecific, currChar, true);
                if (!RichUtils.isLatinLetter.test(currChar))
                    return new FieldSwitch(FieldSwitchType.Error, "", "");
                var switchName = currChar;
                var lastPos = this.modelIterator.getAbsolutePosition();
                if (this.moveIteratorToNextChar()) {
                    currChar = this.modelIterator.getCurrentChar();
                    if (RichUtils.isLatinLetter.test(currChar))
                        switchName += currChar;
                    else
                        this.modelIterator.setPosition(lastPos);
                }
                else
                    this.modelIterator.setPosition(lastPos);
                return this.makeSwitchInfo(FieldSwitchType.FieldSpecific, switchName, false);
        }
    }
    makeSwitchInfo(switchType, switchName, needArgument) {
        var switchArg = this.getSwitchArgument(needArgument);
        if (switchArg === null || needArgument && switchArg.length == 0)
            return new FieldSwitch(FieldSwitchType.Error, "", "");
        return new FieldSwitch(switchType, switchName, switchArg);
    }
    getSwitchArgument(needArgument) {
        if (!this.moveIteratorToNextChar())
            return needArgument ? null : "";
        if (!this.skipWhitespaces())
            return null;
        var parseResult = this.parseSwitchOrFieldArgument();
        if (!parseResult)
            return needArgument ? null : "";
        var resArg = parseResult.argListChars.join("");
        if (resArg.length == 0)
            return null;
        return resArg;
    }
    parseSwitchOrFieldArgument() {
        if (this.modelIterator.run.getType() == RunType.FieldCodeEndRun)
            return null;
        var currChar = this.modelIterator.getCurrentChar();
        if (FieldCodeParserHelper.isBackslesh(currChar)) {
            this.modelIterator.moveToPrevChar();
            return null;
        }
        var resList = [];
        var startFieldStackSize = this.fieldsStack.length;
        var lastFieldStackLength = startFieldStackSize;
        var needSearchNextQuote = FieldCodeParserHelper.isQuote(currChar);
        if (needSearchNextQuote)
            if (!this.moveIteratorToNextChar())
                return null;
        var lastSymbolIsQuote = !needSearchNextQuote;
        do {
            currChar = this.modelIterator.getCurrentChar();
            if (needSearchNextQuote) {
                if (FieldCodeParserHelper.isQuote(currChar)) {
                    var prevChar = resList[resList.length - 1];
                    if (!(prevChar && FieldCodeParserHelper.isBackslesh(prevChar))) {
                        lastSymbolIsQuote = true;
                        lastFieldStackLength = this.fieldsStack.length;
                        this.moveIteratorToNextChar();
                        break;
                    }
                }
            }
            else if (FieldCodeParserHelper.isWhitespaceAndTextRunType(currChar, this.modelIterator.run.getType()))
                break;
            resList.push(currChar);
            lastFieldStackLength = this.fieldsStack.length;
        } while (this.moveIteratorToNextChar());
        if (startFieldStackSize != lastFieldStackLength || !lastSymbolIsQuote)
            return null;
        return { argListChars: resList, quoted: needSearchNextQuote };
    }
}