/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter UniSharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

const path = require('path');
const Utils = require("./../unisharp/Utils");
const Hashtable = require("./../unisharp/Hashtable");
const RefOutArgWrapper = require("./../unisharp/RefOutArgWrapper");
const StringBuilder = require("./../unisharp/StringBuilder");
const Stream = require("./../unisharp/Stream");
const MemoryStream = require("./../unisharp/MemoryStream");
const XmlWriterSettings = require("./../unisharp/XmlWriterSettings");
const XmlWriter = require("./../unisharp/XmlWriter");
const XmlDocument = require("./../unisharp/XmlDocument");

const FileFormatClass = require("./FileFormatClass");
const MiscHelper = require("./../util/MiscHelper");
const FileFormatsHelper = require("./../util/FileFormatsHelper");
const CorrectDocumentParam = require("./CorrectDocumentParam");
const CreateDocumentParam = require("./CreateDocumentParam");
const UnitextContainerType = require("./UnitextContainerType");
const UnitextContainer = require("./UnitextContainer");
const UnitextPagebreak = require("./UnitextPagebreak");
const UnitextStyledFragment = require("./UnitextStyledFragment");
const GetPlaintextParam = require("./GetPlaintextParam");
const UnitextItem = require("./UnitextItem");
const FileFormat = require("./FileFormat");
const ChangeTextPosInfo = require("./internal/uni/ChangeTextPosInfo");
const UnilayPage = require("./UnilayPage");
const UnitextStyle = require("./UnitextStyle");
const GetHtmlParam = require("./GetHtmlParam");

/**
 * Unitext - документ
 * 
 */
class UnitextDocument extends UnitextItem {
    
    constructor() {
        super();
        this.content = null;
        this.innerDocuments = new Array();
        this.pages = new Array();
        this.styles = new Array();
        this.sections = new Array();
        this.sourceFormat = FileFormat.UNKNOWN;
        this.sourceFileName = null;
        this.sourceFilePages = null;
        this.errorMessage = null;
        this.attrs = new Hashtable();
        this.sourcePlainText = null;
        this.htmlStyles = null;
        this.defaultGetHtmlParam = null;
    }
    
    toString() {
        let res = (String(this.sourceFormat) + (this.sourceFileName === null ? "" : " :") + ((this.sourceFileName != null ? this.sourceFileName : "")));
        if (this.sourceFilePages !== null) 
            res = (res + " (стр." + this.sourceFilePages + ")");
        return res;
    }
    
    clone() {
        const UnitextPagesection = require("./UnitextPagesection");
        let res = new UnitextDocument();
        res._cloneFrom(this);
        for (const s of this.styles) {
            res.styles.push(s.clone(false));
        }
        if (this.content !== null) {
            res.content = this.content.clone();
            if (this.content.m_StyledFrag !== null) 
                res.content.m_StyledFrag = this.content.m_StyledFrag.clone();
        }
        for (const d of this.innerDocuments) {
            res.innerDocuments.push(Utils.as(d.clone(), UnitextDocument));
        }
        for (const s of this.sections) {
            res.sections.push(Utils.as(s.clone(), UnitextPagesection));
        }
        res.pages = Array.from(this.pages);
        res.sourceFormat = this.sourceFormat;
        res.sourceFileName = this.sourceFileName;
        res.sourceFilePages = this.sourceFilePages;
        res.sourceInfo = this.sourceInfo;
        res.errorMessage = this.errorMessage;
        res.sourcePlainText = this.sourcePlainText;
        res.defaultGetHtmlParam = this.defaultGetHtmlParam;
        res.htmlStyles = this.htmlStyles;
        for (const a of this.attrs.entries) {
            res.attrs.put(a.key, a.value);
        }
        this.refreshParents();
        return res;
    }
    
    get isInline() {
        return false;
    }
    
    /**
     * Сгенерировать плоский текст
     * @param res куда записать
     * @param pars параметры генерации
     * 
     */
    getPlaintext(res, pars = null) {
        if (this.sourcePlainText !== null) {
            res.append(this.sourcePlainText);
            return;
        }
        if (pars === null) 
            pars = new GetPlaintextParam();
        if (pars.setPositions) 
            this.beginChar = res.length;
        if (this.content !== null) {
            let ii = null;
            if (this.content.m_StyledFrag !== null && pars.setPositions) 
                ii = new ChangeTextPosInfo(this.content.m_StyledFrag, this.content);
            this.content.getPlaintext(res, pars);
            if (ii !== null) 
                ii.restore(res.length);
        }
        if (this.innerDocuments.length > 0 && pars.useInnerDocuments) {
            for (const d of this.innerDocuments) {
                if (res.length > 0) {
                    if (pars.newLine !== null) {
                        res.append(pars.newLine);
                        res.append(pars.newLine);
                    }
                    if (pars.pageBreak !== null) 
                        res.append(pars.pageBreak);
                }
                d.getPlaintext(res, pars);
            }
        }
        if (pars.setPositions) {
            this.endChar = res.length - 1;
            if (this.sections.length === 1) 
                this.sections[0].endChar = this.endChar;
            else if (this.sections.length > 1 && this.content !== null) {
                let its = new Array();
                this.getAllItems(its, 0);
                let i0 = -1;
                for (const it of its) {
                    let ps = it.pageSection;
                    if (ps === null) 
                        continue;
                    let ii = this.sections.indexOf(ps);
                    if (ii < i0) 
                        continue;
                    i0 = ii;
                    if (it.beginChar > 0 && ps.beginChar === 0) 
                        ps.beginChar = it.beginChar;
                    if (it.endChar > ps.endChar) 
                        ps.endChar = it.endChar;
                }
                this.sections[0].beginChar = 0;
                this.sections[this.sections.length - 1].endChar = this.endChar;
            }
        }
    }
    
    /**
     * Сгенерировать плоский текст
     * @param pars параметры генерации
     * @return результат
     * 
     */
    getPlaintextString(pars = null) {
        let res = new StringBuilder();
        this.getPlaintext(res, pars);
        if (res.length === 0) 
            return null;
        return res.toString();
    }
    
    /**
     * Сгенерировать HTML
     * @param res куда записать
     * @param par параметры генерации
     * 
     */
    getHtml(res, par = null) {
        if (par === null) 
            par = this.defaultGetHtmlParam;
        if (par === null) 
            par = new GetHtmlParam();
        if (!par.callBefore(this, res)) 
            return;
        if (par.outHtmlAndBodyTags) {
            res.append("<html><meta charset=\"utf-8\"/>");
            if (!Utils.isNullOrEmpty(par.title)) {
                res.append("\r\n<title>");
                MiscHelper.correctHtmlValue(res, par.title, false, false, false);
                res.append("</title>");
            }
            if (!Utils.isNullOrEmpty(this.htmlStyles)) {
                res.append("\r\n<style>");
                res.append(this.htmlStyles);
                res.append("</style>");
            }
            res.append("<body>");
        }
        if (this.id !== null) 
            res.append("<a name=\"").append(this.id).append("\"> </a>");
        if (this.errorMessage !== null) {
            res.append("\r\n<div style=\"border:2pt solid red\">");
            MiscHelper.correctHtmlValue(res, this.errorMessage, false, false, false);
            res.append("</div>");
        }
        if (this.content !== null) {
            this.content.getHtml(res, par);
            par._outFootnotes(res);
            par._outEndnotes(res);
            if (par !== null && res.length > par.maxHtmlSize) 
                res.append("\r\n<br/><div style=\"color:red\"><b>ВНИМАНИЕ!</b> Размер Html превышает установленные ").append(par.maxHtmlSize).append(" символов, часть информации не попало в Html.</div>");
        }
        if (par === null || par.useInnerDocuments) {
            for (const d of this.innerDocuments) {
                res.append("\r\n<HR/><H2>Внутренний документ ").append(((d.sourceFileName != null ? d.sourceFileName : ""))).append("</H1>");
                d.getHtml(res, par);
            }
        }
        par.callAfter(this, res);
        if (par.outHtmlAndBodyTags) 
            res.append("\r\n</body></html>");
    }
    
    /**
     * Сгенерировать HTML
     * @param par параметры генерации
     * @return результат
     */
    getHtmlString(par = null) {
        let res = new StringBuilder();
        this.getHtml(res, par);
        return res.toString();
    }
    
    /**
     * Сериализовать в XML. Потом можно восстановить фукнцией FromXml().
     * @param xml куда сериализовать
     * 
     */
    getXml(xml) {
        xml.writeStartElement("doc");
        if (this.sourceFormat !== FileFormat.UNKNOWN) 
            xml.writeAttributeString("format", this.sourceFormat.toString().toLowerCase());
        if (this.sourceFileName !== null) 
            xml.writeAttributeString("filename", MiscHelper.correctXmlValue(path.basename(this.sourceFileName)));
        if (this.sourceFilePages !== null) 
            xml.writeAttributeString("filepages", this.sourceFilePages);
        if (this.errorMessage !== null) 
            xml.writeAttributeString("error", MiscHelper.correctXmlValue(this.errorMessage));
        for (const a of this.attrs.entries) {
            xml.writeStartElement("attr");
            xml.writeAttributeString("name", a.key);
            xml.writeAttributeString("val", Utils.notNull(MiscHelper.correctXmlValue(a.value), ""));
            xml.writeEndElement();
        }
        if (this.sourcePlainText !== null) 
            xml.writeElementString("sourcetext", Utils.encodeBase64(MiscHelper.encodeStringUtf8(this.sourcePlainText, false)));
        if (this.htmlStyles !== null) 
            xml.writeElementString("htmlstyles", this.htmlStyles);
        if (this.styles.length > 0) {
            xml.writeStartElement("styles");
            for (const s of this.styles) {
                s.getXml(xml);
            }
            xml.writeEndElement();
        }
        for (const s of this.sections) {
            s.getXml(xml);
        }
        if (this.pages.length > 0) {
            xml.writeStartElement("pages");
            for (const p of this.pages) {
                p.getXml(xml);
            }
            xml.writeEndElement();
        }
        if (this.content !== null) {
            xml.writeStartElement("content");
            this.content.getXml(xml);
            xml.writeEndElement();
            if (this.content.m_StyledFrag !== null) 
                this.content.m_StyledFrag.getXml(xml, null, false);
        }
        for (const d of this.innerDocuments) {
            d.getXml(xml);
        }
        xml.writeEndElement();
    }
    
    /**
     * Десериализовать из XML, полученный функцией GetXml().
     * @param xml корневой узел, куда сериализовали
     * 
     */
    fromXml(xml) {
        const UnitextHelper = require("./internal/uni/UnitextHelper");
        const UnitextPagesection = require("./UnitextPagesection");
        if (xml.attributes !== null) {
            for (const a of xml.attributes) {
                if (a.local_name === "format") {
                    try {
                        this.sourceFormat = FileFormat.of(a.value);
                    } catch (ex592) {
                    }
                }
                else if (a.local_name === "filename") 
                    this.sourceFileName = a.value;
                else if (a.local_name === "filepages") 
                    this.sourceFilePages = a.value;
                else if (this.errorMessage !== null) 
                    this.errorMessage = a.value;
            }
        }
        for (const x of xml.child_nodes) {
            if (x.local_name === "content") {
                for (const xx of x.child_nodes) {
                    this.content = UnitextHelper.createItem(xx, this);
                    this.content.parent = this;
                    break;
                }
            }
            else if (x.local_name === "sourcetext") {
                try {
                    this.sourcePlainText = MiscHelper.decodeStringUtf8(Utils.decodeBase64(x.inner_text), 0, -1);
                } catch (ex) {
                }
            }
            else if (x.local_name === "section") {
                let s = new UnitextPagesection();
                s.fromXml(x);
                this.sections.push(s);
            }
            else if (x.local_name === "doc") {
                let d = new UnitextDocument();
                d.fromXml(x);
                this.innerDocuments.push(d);
            }
            else if (x.local_name === "attr" && x.attributes !== null) {
                try {
                    this.attrs.put(Utils.getXmlAttrByName(x.attributes, "name").value, Utils.getXmlAttrByName(x.attributes, "val").value);
                } catch (ex593) {
                }
            }
            else if (x.local_name === "styles") {
                this.styles.splice(0, this.styles.length);
                for (const xx of x.child_nodes) {
                    let sty = new UnitextStyle();
                    sty.fromXml(xx);
                    this.styles.push(sty);
                }
            }
            else if (x.local_name === "htmlstyles") 
                this.htmlStyles = x.inner_text;
            else if (x.local_name === "pages") {
                this.pages.splice(0, this.pages.length);
                for (const xx of x.child_nodes) {
                    let page = new UnilayPage();
                    page.restore(xx);
                    this.pages.push(page);
                }
            }
            else if (x.local_name === "stylefrag" && this.content !== null) {
                this.content.m_StyledFrag = new UnitextStyledFragment();
                this.content.m_StyledFrag.doc = this;
                this.content.m_StyledFrag.fromXml(x);
            }
        }
        this.getAllItems(null, 0);
    }
    
    optimize(isContent, pars) {
        if (this.content !== null) 
            this.content = this.content.optimize(true, pars);
        for (const s of this.sections) {
            for (const it of s.items) {
                if (it.content !== null) 
                    it.content = it.content.optimize(true, pars);
            }
        }
        for (const d of this.innerDocuments) {
            d.optimize(false, pars);
        }
        return this;
    }
    
    // Обновить ссылки на родительские элементы
    refreshParents() {
        this.getAllItems(null, 0);
    }
    
    /**
     * После OCR-распознавания обновить СТП (Content) на основе нового ТГП (Pages)
     */
    refreshContentByPages() {
        const UnitextCorrHelper = require("./internal/uni/UnitextCorrHelper");
        const UnilayoutHelper = require("./internal/uni/UnilayoutHelper");
        if (this.pages.length === 0) 
            return;
        UnilayoutHelper.createContentFromPages(this, true);
        UnitextCorrHelper.removeFalseNewLines(this, true, null);
        this.refreshParents();
    }
    
    get contentItems() {
        let res = new Array();
        this.getAllItems(res, 0);
        return res;
    }
    
    getAllItems(res, lev = 0) {
        if (res !== null) 
            res.push(this);
        if (this.content !== null) {
            this.content.parent = this;
            this.content.getAllItems(res, lev + 1);
        }
        for (const d of this.innerDocuments) {
            d.parent = this;
            d.getAllItems(res, lev + 1);
        }
        let tmp = null;
        if (res !== null) 
            tmp = new Array();
        for (const s of this.sections) {
            s.parent = this;
            s.getAllItems(tmp, 0);
        }
        if (lev === 0 && res !== null) {
            for (const it of res) {
                if (it.parent !== null && it.pageSectionId !== null) {
                    if (it.pageSection === it.parent.pageSection) 
                        it.pageSectionId = null;
                }
            }
        }
        if (tmp !== null) {
            for (const it of tmp) {
                if (it.parent !== null && it.pageSectionId !== null) {
                    if (it.pageSection === it.pageSection.pageSection) 
                        it.pageSectionId = null;
                }
            }
        }
    }
    
    // Откорректировать документ
    correct(corrPars) {
        const UnitextCorrHelper = require("./internal/uni/UnitextCorrHelper");
        if (corrPars === null) 
            corrPars = new CorrectDocumentParam();
        let ret = false;
        if (corrPars.restoreTables) {
            if (UnitextCorrHelper.restoreTables(this)) 
                ret = true;
        }
        if (corrPars.removePageBreakNumeration) {
            let fty = FileFormatsHelper.getFormatClass(this.sourceFormat);
            if (this.sourceFormat === FileFormat.TXT || fty === FileFormatClass.PAGELAYOUT || fty === FileFormatClass.IMAGE) 
                UnitextCorrHelper.removePageBreakNumeration(this);
        }
        if (corrPars.removeFalseNewLines) 
            UnitextCorrHelper.removeFalseNewLines(this, corrPars.replaceNbspBySpace, corrPars);
        if (corrPars.restoreTextFootnotes) 
            UnitextCorrHelper.restoreTextFootnotes(this);
        if (corrPars.optimizeStructure && corrPars.removeEmptyTables) 
            this.optimize(false, CreateDocumentParam._new361(corrPars));
        return ret;
    }
    
    getStyledFragment(textPos = -1) {
        if (this.content !== null) {
            if (this.content.m_StyledFrag === null) 
                return null;
            return this.content.getStyledFragment(textPos);
        }
        return null;
    }
    
    /**
     * Встроить контейнер в дерево элементов. 
     * ВНИМАНИЕ! Встраивание возможно только после вызова GetPlaintext(), 
     * когда значения BeginChar и EndChar установлены у всех элементов, и встраивание происходит относительно этих значений. 
     * Идентификатор у встраиваемого контейнера устанавливать самим, если нужно потом производить к нему навигацию в Html.
     * @param cnt встраиваемый контейнер (любой наследный от контейнера класс) с установленными значениями BeginChar и EndChar
     * @param plaintext сгенерированный плоский текст, относительно которого идёт позиционирование
     * @param _parent возможный контейнерный блок (если известен), может быть null
     * @return получилось ли встроить
     * 
     */
    implantate(cnt, plaintext, _parent = null) {
        const UnitextImplantateHelper = require("./internal/uni/UnitextImplantateHelper");
        if (this.content === null || cnt === null) 
            return false;
        if (((cnt.beginChar < 0) || (cnt.endChar < cnt.beginChar) || cnt.endChar >= plaintext.length) || cnt.endChar <= 0) {
            let ppp = Utils.as(this.content, UnitextContainer);
            if (ppp === null) {
                ppp = UnitextContainer._new595(this.content.beginChar, this.content.endChar, this);
                ppp.children.push(this.content);
                this.content.parent = ppp;
            }
            cnt.beginChar = (cnt.endChar = 0);
            ppp.children.splice(0, 0, cnt);
            cnt.parent = ppp;
            return true;
        }
        if (_parent !== null) {
            UnitextImplantateHelper.implantate(_parent, cnt, plaintext, null);
            return cnt.parent !== null;
        }
        let res = UnitextImplantateHelper.implantate(this.content, cnt, plaintext, null);
        if (res === null) 
            return false;
        this.content = res;
        return cnt.parent !== null;
    }
    
    /**
     * Встроить гиперссылку в дерево элементов. 
     * ВНИМАНИЕ! Встраивание возможно только после вызова GetPlaintext(), 
     * когда значения BeginChar и EndChar установлены у всех элементов, и встраивание происходит относительно этих значений. 
     * Идентификатор у встраиваемого контейнера устанавливать самим, если нужно потом производить к нему навигацию в Html.
     * @param hl встраиваемая гиперссылка с установленными значениями BeginChar и EndChar
     * @param plaintext сгенерированный плоский текст, относительно которого идёт позиционирование
     * @param _parent возможный контейнерный блок (если известен), может быть null
     * @return получилось ли встроить
     */
    implantateHyperlink(hl, plaintext, _parent = null) {
        if (this.content === null) 
            return false;
        let cnt = new UnitextContainer();
        cnt.beginChar = hl.beginChar;
        cnt.endChar = hl.endChar;
        if (!this.implantate(cnt, plaintext, _parent)) 
            return false;
        if (cnt.parent === null) 
            return false;
        if (!cnt.parent.replaceChild(cnt, hl)) 
            return false;
        hl.content = cnt.optimize(true, null);
        if (hl.content !== null) 
            hl.content.parent = hl;
        return true;
    }
    
    /**
     * Встроить в дерево структурирующий блок UnitextDocblock. 
     * Его идентификатор Id нужно устанавливать самим, если нужно.
     * @param beginHead начальная позиция заголовка блока
     * @param beginBody начальная позиция тела блока
     * @param beginTail начальная позиция подписи (если = beginBody, то отсутствует)
     * @param beginAppendix начальная позиция приложения (если = end, то отсутствует)
     * @param end конечная позиция
     * @param plaintext сгенерированный плоский текст, относительно которого идёт позиционирование
     * @param _parent возможный вышележащий блок
     * @return в случае успеха вернёт указатель встроенный блок, null - если не получилось
     */
    implantateBlock(beginHead, beginBody, beginTail, beginAppendix, end, plaintext, _parent = null) {
        const UnitextImplantateHelper = require("./internal/uni/UnitextImplantateHelper");
        let res = null;
        if ((_parent !== null && _parent.appendix !== null && beginHead >= _parent.appendix.beginChar) && end <= _parent.appendix.endChar) {
            let wrapres596 = new RefOutArgWrapper(res);
            let cnt = UnitextImplantateHelper.implantateBlock(_parent.appendix, beginHead, beginBody, beginTail, beginAppendix, end, plaintext, null, wrapres596);
            res = wrapres596.value;
            if (cnt !== null) 
                _parent.appendix = cnt;
        }
        else if ((_parent !== null && _parent.body !== null && beginHead >= _parent.beginChar) && end <= _parent.endChar) {
            let wrapres597 = new RefOutArgWrapper(res);
            let cnt = UnitextImplantateHelper.implantateBlock(_parent.body, beginHead, beginBody, beginTail, beginAppendix, end, plaintext, null, wrapres597);
            res = wrapres597.value;
            if (cnt !== null) 
                _parent.body = cnt;
        }
        else {
            let wrapres598 = new RefOutArgWrapper(res);
            let cnt = UnitextImplantateHelper.implantateBlock(this.content, beginHead, beginBody, beginTail, beginAppendix, end, plaintext, null, wrapres598);
            res = wrapres598.value;
            if (cnt !== null) 
                this.content = cnt;
        }
        return res;
    }
    
    /**
     * Удалить элемент по его идентификатору Id. 
     * Сейчас работает только для тех элементов, которые были встроены через Implantate
     * @param _id идентификатор удаляемого элемента
     * @return удалось ли удалить
     */
    removeItemById(_id) {
        const UnitextImplantateHelper = require("./internal/uni/UnitextImplantateHelper");
        if (this.content === null) 
            return false;
        let cou = 0;
        let wrapcou599 = new RefOutArgWrapper(cou);
        this.content = UnitextImplantateHelper.clear(this.content, _id, null, UnitextContainerType.UNDEFINED, null, wrapcou599);
        cou = wrapcou599.value;
        return cou > 0;
    }
    
    /**
     * Удалить все гиперссылки, сделав их обычными текстами
     * @param subString если задан, то url должен содержать эту подстроку. Подстрок м.б. несколько, разделитель - точка с запятой.
     * @return количество удалённых гиперссылок
     */
    removeAllHyperlinks(subString = null, pars = null) {
        const UnitextHyperlink = require("./UnitextHyperlink");
        let its = new Array();
        this.getAllItems(its, 0);
        let change = 0;
        let substrs = new Array();
        if (subString !== null) 
            substrs.splice(substrs.length, 0, ...Utils.splitString(subString.toUpperCase(), ';', false));
        for (const it of its) {
            let hl = Utils.as(it, UnitextHyperlink);
            if (hl === null) 
                continue;
            if (substrs.length > 0) {
                if (hl.href === null) {
                    if (!substrs.includes("")) 
                        continue;
                }
                else {
                    let k = 0;
                    for (k = 0; k < substrs.length; k++) {
                        if (!Utils.isNullOrEmpty(substrs[k]) && hl.href.toUpperCase().includes(substrs[k])) 
                            break;
                    }
                    if (k >= substrs.length) {
                        if (((hl.href.indexOf(' ') < 0) && hl.href.indexOf('.') > 0) || hl.isInternal) 
                            continue;
                    }
                }
            }
            if (hl.parent !== null && hl.content !== null) {
                if (hl.ignorable) 
                    hl.content.ignorable = true;
                if (hl.parent.replaceChild(hl, hl.content)) 
                    change++;
            }
        }
        if (change > 0) {
            if (pars === null) 
                pars = new CreateDocumentParam();
            this.optimize(false, pars);
        }
        return change;
    }
    
    /**
     * Сгенерировать внутренние идентификаторы у элементов. 
     * Если у элемента Id установлен, то он не меняется. 
     * У колонтитулов Id не устанавливается. По умолчанию, Id генерируются при создании документа.
     */
    generateIds() {
        let ids = new Hashtable();
        let all = new Array();
        this.getAllItems(all, 0);
        for (const it of all) {
            if (it.innerTag !== null && it.id !== null && it.id.startsWith(it.innerTag)) {
                let n = 0;
                let wrapn600 = new RefOutArgWrapper();
                let inoutres601 = Utils.tryParseInt(it.id.substring(it.innerTag.length), wrapn600);
                n = wrapn600.value;
                if (inoutres601) {
                    if (!ids.containsKey(it.innerTag)) 
                        ids.put(it.innerTag, n);
                    else if (ids.get(it.innerTag) < n) 
                        ids.put(it.innerTag, n);
                }
            }
        }
        for (const it of all) {
            if (it.innerTag !== null && it !== this && it.id === null) {
                let _id = 0;
                let wrapid602 = new RefOutArgWrapper();
                let inoutres603 = ids.tryGetValue(it.innerTag, wrapid602);
                _id = wrapid602.value;
                if (!inoutres603) 
                    ids.put(it.innerTag, (_id = 1));
                else 
                    ids.put(it.innerTag, ++_id);
                it.id = (it.innerTag + _id);
            }
        }
    }
    
    /**
     * Объединить содержимое с содержимым другого документа
     * @param doc другой документ
     */
    mergeWith(doc) {
        let cnt = Utils.as(this.content, UnitextContainer);
        let cnt2 = Utils.as(doc.content, UnitextContainer);
        if (cnt === null) {
            cnt = new UnitextContainer();
            if (this.content !== null) 
                cnt.children.push(this.content);
            this.content = cnt;
        }
        cnt.children.push(new UnitextPagebreak());
        if (cnt2 !== null) 
            cnt.children.splice(cnt.children.length, 0, ...cnt2.children);
        else if (doc.content !== null) 
            cnt.children.push(doc.content);
        this.sections.splice(this.sections.length, 0, ...doc.sections);
    }
    
    /**
     * Преобразовать в байтовый поток (со сжатием). Восстанавливать потом функцией Deserialize().
     * @return результат
     */
    serialize() {
        const ArchiveHelper = require("./../util/ArchiveHelper");
        let tmp = new StringBuilder();
        let xml = XmlWriter.createString(tmp, this._newXmlWriterSettings_604("UTF-8", false)); 
        try {
            this.getXml(xml);
        }
        finally {
            xml.close();
        }
        let str = tmp.toString();
        let dat = MiscHelper.encodeStringUtf8(str, false);
        return ArchiveHelper.compressGZip(dat);
    }
    
    /**
     * Восстановить документ из байтового потока, полученного функцией Serialize(). 
     * Если что не так, то выдаст Exception.
     * @param dat массив байт
     */
    deserialize(dat) {
        const ArchiveHelper = require("./../util/ArchiveHelper");
        if (dat === null || dat.length === 0) 
            return;
        dat = ArchiveHelper.decompressGZip(dat);
        let head = Utils.decodeString("ASCII", dat, 0, (dat.length < 100 ? dat.length : 100));
        let ii = head.indexOf("utf-16");
        if (ii > 0) {
            dat[ii + 4] = '8'.charCodeAt(0);
            dat[ii + 5] = '"'.charCodeAt(0);
            dat[ii + 6] = ' '.charCodeAt(0);
        }
        let mem = new MemoryStream(dat); 
        try {
            let xml = new XmlDocument();
            xml.loadStream(mem);
            this.fromXml(xml.document_element);
            return;
        }
        finally {
            mem.close();
        }
    }
    
    get innerTag() {
        return "doc";
    }
    
    /**
     * Найти элемент по его идентификатору
     * @param _id идентификатор элемента
     * @return результат или null
     */
    findById(_id) {
        if (this.id === _id) 
            return this;
        let res = null;
        if (this.content !== null) {
            if ((((res = this.content.findById(_id)))) !== null) 
                return res;
        }
        for (const doc of this.innerDocuments) {
            if ((((res = doc.findById(_id)))) !== null) 
                return res;
        }
        return null;
    }
    
    static _new47(_arg1) {
        let res = new UnitextDocument();
        res.sourceFormat = _arg1;
        return res;
    }
    
    static _new69(_arg1, _arg2) {
        let res = new UnitextDocument();
        res.sourceFileName = _arg1;
        res.sourceFormat = _arg2;
        return res;
    }
    
    static _new379(_arg1, _arg2) {
        let res = new UnitextDocument();
        res.sourceFormat = _arg1;
        res.errorMessage = _arg2;
        return res;
    }
    
    static _new393(_arg1, _arg2) {
        let res = new UnitextDocument();
        res.sourceFormat = _arg1;
        res.sourceFileName = _arg2;
        return res;
    }
    
    static _new395(_arg1) {
        let res = new UnitextDocument();
        res.sourcePlainText = _arg1;
        return res;
    }
    
    static _new493(_arg1, _arg2) {
        let res = new UnitextDocument();
        res.sourceFormat = _arg1;
        res.attrs = _arg2;
        return res;
    }
    
    _newXmlWriterSettings_604(_arg1, _arg2) {
        let res = new XmlWriterSettings();
        res.encoding = _arg1;
        res.indent = _arg2;
        return res;
    }
}


module.exports = UnitextDocument