/*
 * 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 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 XmlWriter = require("./../unisharp/XmlWriter");
const XmlDocument = require("./../unisharp/XmlDocument");

/**
 * Набор полезных функций
 */
class MiscHelper {
    
    /**
     * Выделить текст из всех форматов, какие только поддерживаются
     * @param fileName 
     * @param content 
     * @param unzipArchives при true будет распаковывать архивы и извлекать тексты из файлов, 
     * сумммарный текст получается конкатенацией
     * @return результат или null, если не получилось
     */
    static extractText(fileName, content = null, unzipArchives = true) {
        const TextHelper = require("./TextHelper");
        return TextHelper.extractText(fileName, content, unzipArchives);
    }
    
    /**
     * Получить hash-значение для строки. В отличие от штатных функций, эта работает 
     * одинаково во всех случаях (например, в C# функция GetHashCode возвращает разные значения 
     * на 32-х и 64-х разрядных компьютерах).
     * @param str строка
     * @return хэш-значение
     */
    static getStringHashCode(str) {
        if (Utils.isNullOrEmpty(str)) 
            return 0;
        let num = 0x15051505;
        let num2 = num;
        for (let i = 0; i < str.length; i += 4) {
            let num0 = ((i + 1) < str.length ? str.charCodeAt(i + 1) : 0);
            num0 <<= 16;
            num0 |= ((str.charCodeAt(i)) & 0xFFFF);
            num = (((((num << 5) + num)) + ((num >> 0x1b)))) ^ num0;
            if ((i + 3) >= str.length) 
                break;
            num0 = str.charCodeAt(i + 3);
            num0 <<= 16;
            num0 |= ((str.charCodeAt(i + 2)) & 0xFFFF);
            num2 = (((((num2 << 5) + num2)) + ((num2 >> 0x1b)))) ^ num0;
        }
        return (num + ((num2 * 0x5d588b65)));
    }
    
    static getCharHashCode(ch) {
        return (ch.charCodeAt(0)) | ((ch.charCodeAt(0)) << 16);
    }
    
    /**
     * Преобразовать строку в float. Pаботает независимо от региональных настроек.
     * @param str строка, разделитель может быть как точка, так и запятая
     * @param res результат
     * @return признак корректности
     */
    static tryParseFloat(str, res) {
        res.value = 0;
        if (Utils.isNullOrEmpty(str)) 
            return false;
        let inoutres656 = Utils.tryParseFloat(str, res);
        if (inoutres656) 
            return true;
        let inoutres655 = Utils.tryParseFloat(Utils.replaceString(str, ',', '.'), res);
        if (str.indexOf(',') >= 0 && inoutres655) 
            return true;
        let inoutres654 = Utils.tryParseFloat(Utils.replaceString(str, '.', ','), res);
        if (str.indexOf('.') >= 0 && inoutres654) 
            return true;
        return false;
    }
    
    /**
     * Преобразовать строку в double. Pаботает независимо от региональных настроек.
     * @param str строка, разделитель может быть как точка, так и запятая
     * @param res результат
     * @return признак корректности
     */
    static tryParseDouble(str, res) {
        res.value = 0;
        if (Utils.isNullOrEmpty(str)) 
            return false;
        let inoutres659 = Utils.tryParseFloat(str, res);
        if (inoutres659) 
            return true;
        let inoutres658 = Utils.tryParseFloat(Utils.replaceString(str, ',', '.'), res);
        if (str.indexOf(',') >= 0 && inoutres658) 
            return true;
        let inoutres657 = Utils.tryParseFloat(Utils.replaceString(str, '.', ','), res);
        if (str.indexOf('.') >= 0 && inoutres657) 
            return true;
        return false;
    }
    
    /**
     * Вывести значение в строку. Не зависит от региональных настроек, разделитель всегда точка.
     * @param val 
     * @return 
     */
    static outDouble(val) {
        return Utils.replaceString(val.toString(), ',', '.');
    }
    
    /**
     * Вывести дату-время. Не зависит от региональных настроек, всегда в формате YYYY.MM.DD HH:MM:SS
     * @param dt дата-время
     * @param timeIgnore не выводить время
     * @return строка с результатом
     */
    static outDateTime(dt, timeIgnore = false) {
        if (timeIgnore) 
            return (String(dt.getFullYear()) + "." + Utils.correctToString((Utils.getMonth(dt)).toString(10), 2, true) + "." + Utils.correctToString((dt.getDate()).toString(10), 2, true));
        else 
            return (String(dt.getFullYear()) + "." + Utils.correctToString((Utils.getMonth(dt)).toString(10), 2, true) + "." + Utils.correctToString((dt.getDate()).toString(10), 2, true) + " " + Utils.correctToString((dt.getHours()).toString(10), 2, true) + ":" + Utils.correctToString((dt.getMinutes()).toString(10), 2, true) + ":" + Utils.correctToString((dt.getSeconds()).toString(10), 2, true));
    }
    
    /**
     * Преобразовать строку в DateTime. Pаботает независимо от региональных настроек.
     * @param str строка с датой в разных форматах написания
     * @return результат или null
     */
    static tryParseDateTime(val) {
        if (Utils.isNullOrEmpty(val)) 
            return null;
        let ints = new Array();
        let isT = false;
        for (let i = 0; i < val.length; i++) {
            if (Utils.isDigit(val[i])) {
                let v = (val.charCodeAt(i)) - 0x30;
                for (++i; i < val.length; i++) {
                    if (!Utils.isDigit(val[i])) 
                        break;
                    else 
                        v = ((v * 10) + (val.charCodeAt(i))) - 0x30;
                }
                ints.push(v);
                if (ints.length === 3 && (i < val.length) && val[i] === 'T') 
                    isT = true;
            }
            else if (ints.length === 3 && val[i] === 'T') 
                isT = true;
        }
        try {
            if (ints.length === 3) {
                if (ints[2] > 1900) 
                    return new Date(ints[2], ints[1] - 1, ints[0], 0, 0, 0);
                if (ints[1] > 12 || ints[2] > 31) 
                    return null;
                return new Date(ints[0], ints[1] - 1, ints[2], 0, 0, 0);
            }
            else if (ints.length === 6 || ((ints.length >= 6 && isT))) {
                if (ints[2] > 1900) 
                    return new Date(ints[2], ints[1] - 1, ints[0], ints[3], ints[4], ints[5]);
                else 
                    return new Date(ints[0], ints[1] - 1, ints[2], ints[3], ints[4], ints[5]);
            }
            else if (ints.length === 7 && ints[0] >= 1990 && ints[1] <= 12) 
                return new Date(ints[0], ints[1] - 1, ints[2], ints[3], ints[4], ints[5]);
        } catch (ex) {
        }
        return null;
    }
    
    /**
     * Преобразовать строку в DateTime. Pаботает независимо от региональных настроек.
     * @param str строка с датой в разных форматах написания
     * @return результат или DateTime.MinValue при ошибке
     */
    static parseDateTime(val) {
        let dt = MiscHelper.tryParseDateTime(val);
        if (dt === null) 
            return Utils.MIN_DATE;
        return dt;
    }
    
    static serializeInt(stream, val) {
        stream.write(Utils.objectToBytes(val, 'int'), 0, 4);
    }
    
    static deserializeInt(stream) {
        let buf = new Uint8Array(4);
        stream.read(buf, 0, 4);
        return Utils.bytesToObject(buf, 0, 'int', 4);
    }
    
    static serializeShort(stream, val) {
        stream.write(Utils.objectToBytes(val, 'short'), 0, 2);
    }
    
    static deserializeShort(stream) {
        let buf = new Uint8Array(2);
        stream.read(buf, 0, 2);
        return Utils.bytesToObject(buf, 0, 'short', 2);
    }
    
    static serializeString(stream, val, deflate = false) {
        const ArchiveHelper = require("./ArchiveHelper");
        if (val === null) {
            MiscHelper.serializeInt(stream, -1);
            return;
        }
        if (Utils.isNullOrEmpty(val)) {
            MiscHelper.serializeInt(stream, 0);
            return;
        }
        let data = MiscHelper.encodeStringUtf8(val, false);
        if (deflate) 
            data = ArchiveHelper.compressGZip(data);
        MiscHelper.serializeInt(stream, data.length);
        stream.write(data, 0, data.length);
    }
    
    static deserializeString(stream, deflate = false) {
        const ArchiveHelper = require("./ArchiveHelper");
        let len = MiscHelper.deserializeInt(stream);
        if (len < 0) 
            return null;
        if (len === 0) 
            return "";
        let data = new Uint8Array(len);
        stream.read(data, 0, data.length);
        if (deflate) 
            data = ArchiveHelper.decompressGZip(data);
        return MiscHelper.decodeStringUtf8(data, 0, -1);
    }
    
    /**
     * Прочитать байтовый массив из потока
     * @param s поток
     * @return результат
     */
    static readStream(s) {
        if (s.canSeek()) {
            if (s.length > (0)) {
                let res = new Uint8Array(s.length);
                s.read(res, 0, res.length);
                return res;
            }
        }
        let buf = new Uint8Array(10000);
        let mem = new MemoryStream();
        let k = 0;
        while (true) {
            let i = s.read(buf, 0, buf.length);
            if (i < 0) 
                break;
            if (i === 0) {
                if ((++k) > 3) 
                    break;
                continue;
            }
            mem.write(buf, 0, i);
            k = 0;
        }
        let arr = mem.toByteArray();
        mem.close();
        return arr;
    }
    
    // Замена стандартной функции, которая очень тормозит
    static endsWith(str, substr) {
        if (str === null || substr === null) 
            return false;
        let i = str.length - 1;
        let j = substr.length - 1;
        if (j > i || (j < 0)) 
            return false;
        for (; j >= 0; j--,i--) {
            if (str[i] !== substr[j]) 
                return false;
        }
        return true;
    }
    
    static endsWithEx(str, substr, substr2, substr3 = null) {
        if (str === null) 
            return false;
        for (let k = 0; k < 3; k++) {
            if (k === 1) 
                substr = substr2;
            else if (k === 2) 
                substr = substr3;
            if (substr === null) 
                continue;
            let i = str.length - 1;
            let j = substr.length - 1;
            if (j > i || (j < 0)) 
                continue;
            for (; j >= 0; j--,i--) {
                if (str[i] !== substr[j]) 
                    break;
            }
            if (j < 0) 
                return true;
        }
        return false;
    }
    
    /**
     * Закодировать строку кодировкой ASCII. Работает на всех платформах.
     * @param str строка
     * @return результат
     */
    static encodeStringAscii(str) {
        if (str === null) 
            return new Uint8Array(0);
        let res = new Uint8Array(str.length);
        for (let j = 0; j < str.length; j++) {
            let i = str.charCodeAt(j);
            if (i < 0x100) 
                res[j] = i;
            else 
                res[j] = '?'.charCodeAt(0);
        }
        return res;
    }
    
    /**
     * Закодировать строку кодировкой windows-1251. Работает на всех платформах.
     * @param str строка
     * @return результат
     */
    static encodeString1251(str) {
        if (str === null) 
            return new Uint8Array(0);
        let res = new Uint8Array(str.length);
        for (let j = 0; j < str.length; j++) {
            let i = str.charCodeAt(j);
            if (i < 0x80) 
                res[j] = i;
            else {
                let b = 0;
                let wrapb660 = new RefOutArgWrapper();
                let inoutres661 = MiscHelper.m_utf_1251.tryGetValue(i, wrapb660);
                b = wrapb660.value;
                if (inoutres661) 
                    res[j] = b;
                else 
                    res[j] = '?'.charCodeAt(0);
            }
        }
        return res;
    }
    
    /**
     * Закодировать строку кодировкой windows-1252. Работает на всех платформах.
     * @param str строка
     * @return результат
     */
    static encodeString1252(str) {
        if (str === null) 
            return new Uint8Array(0);
        let res = new Uint8Array(str.length);
        for (let j = 0; j < str.length; j++) {
            let i = str.charCodeAt(j);
            if ((i < 0x80) || i >= 0xA0) 
                res[j] = i;
            else {
                let b = 0;
                let wrapb662 = new RefOutArgWrapper();
                let inoutres663 = MiscHelper.m_utf_1252.tryGetValue(i, wrapb662);
                b = wrapb662.value;
                if (inoutres663) 
                    res[j] = b;
                else 
                    res[j] = '?'.charCodeAt(0);
            }
        }
        return res;
    }
    
    /**
     * Декодировать строку из массива в кодировке Ascii. Работает на всех платформах.
     * @param dat байтовый масств
     * @param pos начальная позиция в массиве
     * @param len длина байт
     * @return строка с результатом
     */
    static decodeStringAscii(dat, pos = 0, len = -1) {
        if (dat === null) 
            return null;
        if (dat.length === 0) 
            return "";
        if (len < 0) 
            len = dat.length - pos;
        let tmp = new StringBuilder();
        for (let j = pos; (j < (pos + len)) && (j < dat.length); j++) {
            let i = dat[j];
            if (i < 0x100) 
                tmp.append(String.fromCharCode(i));
            else 
                tmp.append('?');
        }
        return tmp.toString();
    }
    
    /**
     * Декодировать строку из массива в кодировке windows-1251. Работает на всех платформах.
     * @param dat байтовый масств
     * @param pos начальная позиция в массиве
     * @param len длина байт
     * @return строка с результатом
     */
    static decodeString1251(dat, pos = 0, len = -1) {
        if (dat === null) 
            return null;
        if (dat.length === 0) 
            return "";
        if (len < 0) 
            len = dat.length - pos;
        let tmp = new StringBuilder();
        for (let j = pos; (j < (pos + len)) && (j < dat.length); j++) {
            let i = dat[j];
            if (i < 0x80) 
                tmp.append(String.fromCharCode(i));
            else if (MiscHelper.m_1251_utf[i] === 0) 
                tmp.append('?');
            else 
                tmp.append(String.fromCharCode(MiscHelper.m_1251_utf[i]));
        }
        return tmp.toString();
    }
    
    /**
     * Декодировать строку из массива в кодировке windows-1252. Работает на всех платформах.
     * @param dat байтовый масств
     * @param pos начальная позиция в массиве
     * @param len длина байт
     * @return строка с результатом
     */
    static decodeString1252(dat, pos = 0, len = -1) {
        if (dat === null) 
            return null;
        if (dat.length === 0) 
            return "";
        if (len < 0) 
            len = dat.length - pos;
        let tmp = new StringBuilder();
        for (let j = pos; (j < (pos + len)) && (j < dat.length); j++) {
            let i = dat[j];
            if ((i < 0x80) || i >= 0xA0) 
                tmp.append(String.fromCharCode(i));
            else if (MiscHelper.m_1252_utf[i] === 0) 
                tmp.append('?');
            else 
                tmp.append(String.fromCharCode(MiscHelper.m_1252_utf[i]));
        }
        return tmp.toString();
    }
    
    /**
     * Закодировать строку в коде UTF-8 с добавлением преамбулы
     * @param str кодируемая строка
     * @param addPreambl добавлять ли преамбулу EF BB BF
     * @return результирующий массив
     */
    static encodeStringUtf8(str, addPreambl = false) {
        if (str === null) 
            return new Uint8Array(0);
        let res = Utils.encodeString("UTF-8", str);
        if (!addPreambl) 
            return res;
        let tmp = new Array();
        tmp.splice(tmp.length, 0, ...Utils.getEncodingPreamble("UTF-8"));
        tmp.splice(tmp.length, 0, ...res);
        return tmp;
    }
    
    /**
     * Декодировать строку из UTF-8. Если есть преамбула, то она проигнорируется.
     * @param dat массив с закодированной строкой
     * @return раскодированная строка
     */
    static decodeStringUtf8(dat, pos = 0, len = -1) {
        if (dat === null) 
            return null;
        if (len < 0) 
            len = dat.length;
        if ((len > 3 && dat[pos] === (0xEF) && dat[pos + 1] === (0xBB)) && dat[pos + 2] === (0xBF)) {
            pos += 3;
            len -= 3;
        }
        return Utils.decodeString("UTF-8", dat, pos, len);
    }
    
    /**
     * Закодировать строку в 2-х байтовой кодировке Unicode, младший байт первый (UTF-16LE).
     * @param str кодируемая строка
     * @return результирующий массив
     */
    static encodeStringUnicode(str) {
        if (Utils.isNullOrEmpty(str)) 
            return new Uint8Array(0);
        let res = new Uint8Array(str.length * 2);
        for (let i = 0; i < str.length; i++) {
            let cod = str.charCodeAt(i);
            res[i * 2] = (cod & 0xFF);
            res[(i * 2) + 1] = (cod >> 8);
        }
        return res;
    }
    
    /**
     * Декодировать строку из 2-х байтовой кодировки Unicode, младший байт первый (UTF-16LE).
     * @param dat массив с закодированной строкой
     * @return раскодированная строка
     */
    static decodeStringUnicode(dat, pos = 0, len = -1) {
        if (dat === null) 
            return null;
        if (len < 0) 
            len = dat.length;
        let res = new StringBuilder();
        for (let i = pos; (i < (pos + len)) && ((i + 1) < dat.length); i += 2) {
            let cod = dat[i + 1];
            cod <<= 8;
            cod |= (dat[i]);
            res.append(String.fromCharCode(cod));
        }
        return res.toString();
    }
    
    /**
     * Закодировать строку в 2-х байтовой кодировке Unicode, старший байт первый (UTF-16BE).
     * @param str кодируемая строка
     * @return результирующий массив
     */
    static encodeStringUnicodeBE(str) {
        if (Utils.isNullOrEmpty(str)) 
            return new Uint8Array(0);
        let res = new Uint8Array(str.length * 2);
        for (let i = 0; i < str.length; i++) {
            let cod = str.charCodeAt(i);
            res[(i * 2) + 1] = (cod & 0xFF);
            res[i * 2] = (cod >> 8);
        }
        return res;
    }
    
    /**
     * Декодировать строку из 2-х байтовой кодировки Unicode, старший байт первый (UTF-16BE).
     * @param dat массив с закодированной строкой
     * @return раскодированная строка
     */
    static decodeStringUnicodeBE(dat, pos = 0, len = -1) {
        if (dat === null) 
            return null;
        if (len < 0) 
            len = dat.length;
        let res = new StringBuilder();
        for (let i = pos; i < (pos + len); i += 2) {
            let cod = dat[i];
            cod <<= 8;
            cod |= (dat[i + 1]);
            res.append(String.fromCharCode(cod));
        }
        return res.toString();
    }
    
    static correctCsvValue(txt, surroundBrackets = false) {
        if (Utils.isNullOrEmpty(txt)) 
            return "";
        let sur = false;
        if (txt.indexOf('"') >= 0) {
            txt = Utils.replaceString(txt, "\"", "\"\"");
            sur = true;
        }
        if (surroundBrackets && txt.indexOf(';') >= 0) 
            sur = true;
        if (surroundBrackets && sur) 
            return ("\"" + txt + "\"");
        else 
            return txt;
    }
    
    static correctJsonValue(txt) {
        if (Utils.isNullOrEmpty(txt)) 
            return "";
        let res = new StringBuilder();
        if (txt !== null) {
            for (const ch of txt) {
                if (ch === '"') 
                    res.append("\\\"");
                else if (ch === '\\') 
                    res.append("\\\\");
                else if (ch === '/') 
                    res.append("\\/");
                else if ((ch.charCodeAt(0)) === 0xD) 
                    res.append("\\r");
                else if ((ch.charCodeAt(0)) === 0xA) 
                    res.append("\\n");
                else if (ch === '\t') 
                    res.append("\\t");
                else if ((ch.charCodeAt(0)) < 0x20) 
                    res.append(' ');
                else 
                    res.append(ch);
            }
        }
        return res.toString();
    }
    
    static correctXmlFile(res) {
        let i = res.toString().indexOf('>');
        if (i > 10 && res.charAt(1) === '?') 
            res.remove(0, i + 1);
        for (i = 0; i < res.length; i++) {
            let ch = res.charAt(i);
            let cod = ch.charCodeAt(0);
            if ((cod < 0x80) && cod >= 0x20) 
                continue;
            if (cod >= 0x400 || (cod < 0x500)) 
                continue;
            res.remove(i, 1);
            res.insert(i, ("&#x" + Utils.correctToString((cod).toString(16).toUpperCase(), 4, true) + ";"));
        }
    }
    
    /**
     * Сериализация объекта, реализующего IXmlReadWriteSupport, в байтовый массив. 
     * Работает одинаково на всех языках программирования.
     * @param obj объект
     * @return результат
     */
    static serializeToBin(obj) {
        const ArchiveHelper = require("./ArchiveHelper");
        if (obj === null) 
            return null;
        let res = new StringBuilder();
        let xml = XmlWriter.createString(res, null); 
        try {
            obj.writeToXml(xml, null);
        }
        finally {
            xml.close();
        }
        MiscHelper.correctXmlFile(res);
        let dat = MiscHelper.encodeStringUtf8(res.toString(), false);
        return ArchiveHelper.compressGZip(dat);
    }
    
    /**
     * Десериализация из байтового массива
     * @param dat массив, полученный функцией SerializeToBin
     * @param obj экземпляр десериализуемого объекта
     */
    static deserializeFromBin(dat, obj) {
        const ArchiveHelper = require("./ArchiveHelper");
        if (dat === null) 
            return;
        let data = ArchiveHelper.decompressGZip(dat);
        let str = MiscHelper.decodeStringUtf8(data, 0, -1);
        let xml = new XmlDocument();
        xml.loadXml(str);
        obj.readFromXml(xml.document_element);
    }
    
    static serializeToBinList(objs) {
        const ArchiveHelper = require("./ArchiveHelper");
        let res = new StringBuilder();
        let xml = XmlWriter.createString(res, null); 
        try {
            xml.writeStartElement("LIST");
            if (objs !== null) {
                for (const obj of objs) {
                    obj.writeToXml(xml, null);
                }
            }
            xml.writeEndElement();
        }
        finally {
            xml.close();
        }
        MiscHelper.correctXmlFile(res);
        let dat = MiscHelper.encodeStringUtf8(res.toString(), false);
        return ArchiveHelper.compressGZip(dat);
    }
    
    static deserializeFromBinList(dat, res) {
        const ArchiveHelper = require("./ArchiveHelper");
        if (dat === null) 
            return 0;
        let data = ArchiveHelper.decompressGZip(dat);
        let str = MiscHelper.decodeStringUtf8(data, 0, -1);
        let xml = new XmlDocument();
        xml.loadXml(str);
        let i = 0;
        for (const x of xml.document_element.child_nodes) {
            if (res !== null) {
                if (i >= res.length) 
                    break;
                res[i].readFromXml(x);
            }
            i++;
        }
        return i;
    }
    
    /**
     * При сохранении значений в XML рекомендуется пропускать через эту функцию. 
     * Иначе если в строке окажутся некоторые символы (например, 0xC), то XML получается некорректным.
     * @param txt исходный текст узла или атрибута
     * @return откорректированный
     */
    static correctXmlValue(txt) {
        if (txt === null) 
            return "";
        let corr = false;
        for (let i = 0; i < txt.length; i++) {
            let cod = txt.charCodeAt(i);
            if (((cod < 0x20) && cod !== 0xD && cod !== 0xA) && cod !== 9) {
                corr = true;
                break;
            }
            else if (cod >= 0xD800 && cod <= 0xDBFF) {
                if ((i + 1) >= txt.length) {
                    corr = true;
                    break;
                }
                i++;
                cod = txt.charCodeAt(i);
                if ((cod < 0xDC00) || cod > 0xDFFF) {
                    corr = true;
                    break;
                }
            }
            else if (cod >= 0xDC00 && cod <= 0xDFFF) {
                corr = true;
                break;
            }
            else if (cod >= 0xFF00) {
                corr = true;
                break;
            }
        }
        if (!corr) 
            return txt;
        let tmp = new StringBuilder(txt);
        for (let i = 0; i < tmp.length; i++) {
            let ch = tmp.charAt(i);
            if ((((ch.charCodeAt(0)) < 0x20) && ch !== '\r' && ch !== '\n') && ch !== '\t') 
                tmp.setCharAt(i, ' ');
            else if ((ch.charCodeAt(0)) >= 0xD800 && (ch.charCodeAt(0)) <= 0xDBFF) {
                if ((i + 1) >= tmp.length) {
                    tmp.setCharAt(i, ' ');
                    break;
                }
                let ch1 = tmp.charAt(i + 1);
                if ((ch1.charCodeAt(0)) >= 0xDC00 && (ch1.charCodeAt(0)) <= 0xDFFF) 
                    i++;
                else 
                    tmp.setCharAt(i, ' ');
            }
            else if ((ch.charCodeAt(0)) >= 0xDC00 && (ch.charCodeAt(0)) <= 0xDFFF) 
                tmp.setCharAt(i, '?');
            else if ((ch.charCodeAt(0)) >= 0xFF00) 
                tmp.setCharAt(i, ' ');
        }
        return tmp.toString();
    }
    
    static correctHtmlChar(res, c) {
        if ((((c.charCodeAt(0)) < 0x20) && c !== '\r' && c !== '\n') && c !== '\t') 
            res.append(' ');
        else if ((c.charCodeAt(0)) > 0xF000) 
            res.append("&#").append((c.charCodeAt(0))).append(";");
        else if ((c.charCodeAt(0)) === 0xA || (c.charCodeAt(0)) === 0xD) 
            res.append("\r\n<br/>");
        else if (c === (String.fromCharCode(0xA0))) 
            res.append("&nbsp;");
        else if (c === '<') 
            res.append("&lt;");
        else if (c === '>') 
            res.append("&gt;");
        else if (c === '&') 
            res.append("&amp;");
        else 
            res.append(c);
    }
    
    /**
     * Подправить HTML-значение при выводе в html
     * @param res результирующий html
     * @param txt текст
     * @param isAttr текст для атрибута (иначе для просто текста между тегами)
     * @param isPureXml 
     * @param encodeAllNonansiChars 
     */
    static correctHtmlValue(res, txt, isAttr = false, isPureXml = false, encodeAllNonansiChars = false) {
        if (txt === null) 
            return;
        if (txt === " ") {
            res.append("&nbsp;");
            return;
        }
        for (let i = 0; i < txt.length; i++) {
            let c = txt[i];
            if ((((c.charCodeAt(0)) < 0x20) && c !== '\r' && c !== '\n') && c !== '\t') 
                res.append(' ');
            else if ((c.charCodeAt(0)) > 0xF000 || ((encodeAllNonansiChars && (c.charCodeAt(0)) >= 0x80))) 
                res.append("&#").append((c.charCodeAt(0))).append(";");
            else if ((c.charCodeAt(0)) === 0xA || (c.charCodeAt(0)) === 0xD) {
                if (i > 0 && (txt.charCodeAt(i - 1)) === 0xD) {
                }
                else if (isAttr || isPureXml) 
                    res.append("\r\n");
                else 
                    res.append("\r\n<br/>");
            }
            else if (c === (String.fromCharCode(0xA0))) {
                if (isPureXml) 
                    res.append("&#").append(0xA0).append(";");
                else 
                    res.append("&nbsp;");
            }
            else if (c === '<') 
                res.append("&lt;");
            else if (c === '>') 
                res.append("&gt;");
            else if (c === '&') 
                res.append("&amp;");
            else if (isAttr && c === '"') 
                res.append("&quot;");
            else 
                res.append(c);
        }
    }
    
    /**
     * Извлечь аргументы командной строки из файла (для string[] args из main)
     * @param fname имя файла
     * @return список аргументов или null, если их нет
     */
    static getCommandlineArgumentsFromFile(fname) {
        const TextHelper = require("./TextHelper");
        let txt = TextHelper.readStringFromFile(fname, false, false);
        let args0 = new Array();
        if (txt !== null) {
            for (let i = 0; i < txt.length; i++) {
                if (!Utils.isWhitespace(txt[i])) {
                    let j = 0;
                    if (txt[i] === '"') {
                        for (j = i + 1; j < txt.length; j++) {
                            if (txt[j] === '"') 
                                break;
                        }
                        if (j < txt.length) {
                            let val = txt.substring(i + 1, i + 1 + j - i - 1);
                            args0.push(val);
                            i = j;
                        }
                    }
                    else {
                        for (j = i + 1; j < txt.length; j++) {
                            if (Utils.isWhitespace(txt[j])) 
                                break;
                        }
                        let val = txt.substring(i, i + j - i);
                        args0.push(val);
                        i = j;
                    }
                }
            }
        }
        if (args0.length > 0) 
            return args0;
        return null;
    }
    
    /**
     * Корректировка XML, отправляемого по rest (для внутреннего использования)
     * @param res 
     * @return 
     */
    static correctRestXml(res) {
        for (let i = 0; (i < 100) && (i < (res.length - 1)); i++) {
            if (res.charAt(i) === '?' && res.charAt(i + 1) === '>') {
                for (i += 2; i < res.length; i++) {
                    if (!Utils.isWhitespace(res.charAt(i))) 
                        break;
                }
                if (i < res.length) 
                    res.remove(0, i);
                break;
            }
        }
        let txt = res.toString();
        return MiscHelper.encodeStringUtf8(txt, false);
    }
    
    /**
     * Это расширение функции Uri.UnescapeDataString, которая иногда раскодирует неправильно
     * @param val 
     * @return 
     */
    static unescapeDataString(val) {
        if (Utils.isNullOrEmpty(val)) 
            return val;
        let str = val;
        let tmp = new StringBuilder();
        let buf = new Uint8Array(1);
        for (let i = 0; i < str.length; i++) {
            let ch = str[i];
            if (ch === '+') {
                tmp.append(' ');
                continue;
            }
            if (ch === '%' && ((i + 2) < str.length)) {
                let code = 0;
                for (let j = 0; j < 2; j++) {
                    ch = str[i + 1 + j];
                    if (Utils.isDigit(ch)) 
                        code = (code * 16) + (((ch.charCodeAt(0)) - ('0'.charCodeAt(0))));
                    else if ("ABCDEF".indexOf(ch) >= 0) 
                        code = (code * 16) + 10 + "ABCDEF".indexOf(ch);
                }
                i += 2;
                buf[0] = code;
                let dd = MiscHelper.decodeString1251(buf, 0, 1);
                if (dd !== null) 
                    tmp.append(dd);
                continue;
            }
            if (ch !== '&' || (i + 3) > str.length) {
                tmp.append(ch);
                continue;
            }
            if (str[i + 1] === '#') {
                let code = 0;
                i += 2;
                for (let j = i; j < str.length; j++) {
                    if (!Utils.isDigit(str[j])) 
                        break;
                    else {
                        i = j;
                        code = (code * 10) + (((str.charCodeAt(i)) - ('0'.charCodeAt(0))));
                    }
                }
                tmp.append(String.fromCharCode(code));
                i++;
            }
            else 
                tmp.append(ch);
        }
        return tmp.toString();
    }
    
    /**
     * Вычисление процента
     * @param val значение
     * @param max максимум
     * @return процент
     */
    static calcPercent(val, max) {
        if (val >= max) 
            return 100;
        if (val <= 0 || max <= 0) 
            return 0;
        if (max < 100000) 
            return Utils.intDiv(val * 100, max);
        return Utils.intDiv(val, (Utils.intDiv(max, 100)));
    }
    
    static static_constructor() {
        MiscHelper.m_1251_utf = null;
        MiscHelper.m_1252_utf = null;
        MiscHelper.m_utf_1251 = null;
        MiscHelper.m_utf_1252 = null;
        MiscHelper.m_1251_utf = new Int32Array(256);
        MiscHelper.m_utf_1251 = new Hashtable();
        for (let i = 0; i < 0x80; i++) {
            MiscHelper.m_1251_utf[i] = i;
        }
        let m_1251_80_BF = new Int32Array([0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457]);
        for (let i = 0; i < 0x40; i++) {
            MiscHelper.m_1251_utf[i + 0x80] = m_1251_80_BF[i];
            MiscHelper.m_utf_1251.put(m_1251_80_BF[i], (i + 0x80));
        }
        for (let i = 0; i < 0x20; i++) {
            MiscHelper.m_1251_utf[i + 0xC0] = ('А'.charCodeAt(0)) + i;
            MiscHelper.m_utf_1251.put(('А'.charCodeAt(0)) + i, (i + 0xC0));
        }
        for (let i = 0; i < 0x20; i++) {
            MiscHelper.m_1251_utf[i + 0xE0] = ('а'.charCodeAt(0)) + i;
            MiscHelper.m_utf_1251.put(('а'.charCodeAt(0)) + i, (i + 0xE0));
        }
        let m_1252_80_9F = new Int32Array([0x20AC, 0, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0, 0x017D, 0, 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0, 0x017E, 0x0178]);
        MiscHelper.m_1252_utf = new Int32Array(256);
        MiscHelper.m_utf_1252 = new Hashtable();
        for (let i = 0; i < 0x100; i++) {
            MiscHelper.m_1252_utf[i] = i;
        }
        for (let i = 0; i < 0x20; i++) {
            if (m_1252_80_9F[i] !== 0) {
                MiscHelper.m_1252_utf[i + 0x80] = m_1252_80_9F[i];
                MiscHelper.m_utf_1252.put(m_1252_80_9F[i], (i + 0x80));
            }
        }
    }
}


MiscHelper.static_constructor();

module.exports = MiscHelper