/*
 * 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
 */

package com.pullenti.util;

/**
 * Набор полезных функций
 */
public class MiscHelper {

    /**
     * Выделить текст из всех форматов, какие только поддерживаются
     * @param fileName 
     * @param content 
     * @param unzipArchives при true будет распаковывать архивы и извлекать тексты из файлов, 
     * сумммарный текст получается конкатенацией
     * @return результат или null, если не получилось
     */
    public static String extractText(String fileName, byte[] content, boolean unzipArchives) throws Exception, java.io.FileNotFoundException, java.io.IOException {
        return TextHelper.extractText(fileName, content, unzipArchives);
    }

    /**
     * Получить hash-значение для строки. В отличие от штатных функций, эта работает 
     * одинаково во всех случаях (например, в C# функция GetHashCode возвращает разные значения 
     * на 32-х и 64-х разрядных компьютерах).
     * @param str строка
     * @return хэш-значение
     */
    public static int getStringHashCode(String str) {
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(str)) 
            return 0;
        int num = 0x15051505;
        int num2 = num;
        for (int i = 0; i < str.length(); i += 4) {
            int num0 = ((i + 1) < str.length() ? (int)str.charAt(i + 1) : 0);
            num0 <<= 16;
            num0 |= (((int)str.charAt(i)) & 0xFFFF);
            num = (((((num << 5) + num)) + ((num >> 0x1b)))) ^ num0;
            if ((i + 3) >= str.length()) 
                break;
            num0 = (int)str.charAt(i + 3);
            num0 <<= 16;
            num0 |= (((int)str.charAt(i + 2)) & 0xFFFF);
            num2 = (((((num2 << 5) + num2)) + ((num2 >> 0x1b)))) ^ num0;
        }
        return (num + ((num2 * 0x5d588b65)));
    }

    public static int getCharHashCode(char ch) {
        return ((int)ch) | (((int)ch) << 16);
    }


    /**
     * Преобразовать строку в float. Pаботает независимо от региональных настроек.
     * @param str строка, разделитель может быть как точка, так и запятая
     * @param res результат
     * @return признак корректности
     */
    public static boolean tryParseFloat(String str, com.pullenti.unisharp.Outargwrapper<Float> res) {
        res.value = 0.0F;
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(str)) 
            return false;
        boolean inoutres658 = com.pullenti.unisharp.Utils.parseFloat(str, null, res);
        if (inoutres658) 
            return true;
        boolean inoutres657 = com.pullenti.unisharp.Utils.parseFloat(str.replace(',', '.'), null, res);
        if (str.indexOf(',') >= 0 && inoutres657) 
            return true;
        boolean inoutres656 = com.pullenti.unisharp.Utils.parseFloat(str.replace('.', ','), null, res);
        if (str.indexOf('.') >= 0 && inoutres656) 
            return true;
        return false;
    }

    /**
     * Преобразовать строку в double. Pаботает независимо от региональных настроек.
     * @param str строка, разделитель может быть как точка, так и запятая
     * @param res результат
     * @return признак корректности
     */
    public static boolean tryParseDouble(String str, com.pullenti.unisharp.Outargwrapper<Double> res) {
        res.value = 0.0;
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(str)) 
            return false;
        boolean inoutres661 = com.pullenti.unisharp.Utils.parseDouble(str, null, res);
        if (inoutres661) 
            return true;
        boolean inoutres660 = com.pullenti.unisharp.Utils.parseDouble(str.replace(',', '.'), null, res);
        if (str.indexOf(',') >= 0 && inoutres660) 
            return true;
        boolean inoutres659 = com.pullenti.unisharp.Utils.parseDouble(str.replace('.', ','), null, res);
        if (str.indexOf('.') >= 0 && inoutres659) 
            return true;
        return false;
    }

    /**
     * Вывести значение в строку. Не зависит от региональных настроек, разделитель всегда точка.
     * @param val 
     * @return 
     */
    public static String outDouble(double val) {
        return ((Double)val).toString().replace(',', '.');
    }

    /**
     * Вывести дату-время. Не зависит от региональных настроек, всегда в формате YYYY.MM.DD HH:MM:SS
     * @param dt дата-время
     * @param timeIgnore не выводить время
     * @return строка с результатом
     */
    public static String outDateTime(java.time.LocalDateTime dt, boolean timeIgnore) {
        if (timeIgnore) 
            return (((Integer)dt.getYear()).toString() + "." + String.format("%02d", dt.getMonthValue()) + "." + String.format("%02d", dt.getDayOfMonth()));
        else 
            return (((Integer)dt.getYear()).toString() + "." + String.format("%02d", dt.getMonthValue()) + "." + String.format("%02d", dt.getDayOfMonth()) + " " + String.format("%02d", dt.getHour()) + ":" + String.format("%02d", dt.getMinute()) + ":" + String.format("%02d", dt.getSecond()));
    }

    /**
     * Преобразовать строку в DateTime. Pаботает независимо от региональных настроек.
     * @param str строка с датой в разных форматах написания
     * @return результат или null
     */
    public static java.time.LocalDateTime tryParseDateTime(String val) {
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(val)) 
            return null;
        java.util.ArrayList<Integer> ints = new java.util.ArrayList<Integer>();
        boolean isT = false;
        for (int i = 0; i < val.length(); i++) {
            if (Character.isDigit(val.charAt(i))) {
                int v = ((int)val.charAt(i)) - 0x30;
                for (++i; i < val.length(); i++) {
                    if (!Character.isDigit(val.charAt(i))) 
                        break;
                    else 
                        v = ((v * 10) + ((int)val.charAt(i))) - 0x30;
                }
                ints.add(v);
                if (ints.size() == 3 && (i < val.length()) && val.charAt(i) == 'T') 
                    isT = true;
            }
            else if (ints.size() == 3 && val.charAt(i) == 'T') 
                isT = true;
        }
        try {
            if (ints.size() == 3) {
                if (ints.get(2) > 1900) 
                    return java.time.LocalDateTime.of(ints.get(2), ints.get(1), ints.get(0), 0, 0, 0);
                if (ints.get(1) > 12 || ints.get(2) > 31) 
                    return null;
                return java.time.LocalDateTime.of(ints.get(0), ints.get(1), ints.get(2), 0, 0, 0);
            }
            else if (ints.size() == 6 || ((ints.size() >= 6 && isT))) {
                if (ints.get(2) > 1900) 
                    return java.time.LocalDateTime.of(ints.get(2), ints.get(1), ints.get(0), ints.get(3), ints.get(4), ints.get(5));
                else 
                    return java.time.LocalDateTime.of(ints.get(0), ints.get(1), ints.get(2), ints.get(3), ints.get(4), ints.get(5));
            }
            else if (ints.size() == 7 && ints.get(0) >= 1990 && ints.get(1) <= 12) 
                return java.time.LocalDateTime.of(ints.get(0), ints.get(1), ints.get(2), ints.get(3), ints.get(4), ints.get(5));
        } catch (Exception ex) {
        }
        return null;
    }

    /**
     * Преобразовать строку в DateTime. Pаботает независимо от региональных настроек.
     * @param str строка с датой в разных форматах написания
     * @return результат или DateTime.MinValue при ошибке
     */
    public static java.time.LocalDateTime parseDateTime(String val) {
        java.time.LocalDateTime dt = tryParseDateTime(val);
        if (dt == null) 
            return java.time.LocalDateTime.MIN;
        return dt;
    }

    public static void serializeInt(com.pullenti.unisharp.Stream stream, int val) throws java.io.IOException {
        stream.write(java.nio.ByteBuffer.allocate(4).order(java.nio.ByteOrder.LITTLE_ENDIAN).putInt(val).array(), 0, 4);
    }

    public static int deserializeInt(com.pullenti.unisharp.Stream stream) throws java.io.IOException {
        byte[] buf = new byte[4];
        stream.read(buf, 0, 4);
        return java.nio.ByteBuffer.wrap(buf, 0, 4).order(java.nio.ByteOrder.LITTLE_ENDIAN).getInt();
    }

    public static void serializeShort(com.pullenti.unisharp.Stream stream, short val) throws java.io.IOException {
        stream.write(java.nio.ByteBuffer.allocate(2).order(java.nio.ByteOrder.LITTLE_ENDIAN).putShort(val).array(), 0, 2);
    }

    public static short deserializeShort(com.pullenti.unisharp.Stream stream) throws java.io.IOException {
        byte[] buf = new byte[2];
        stream.read(buf, 0, 2);
        return java.nio.ByteBuffer.wrap(buf, 0, 2).order(java.nio.ByteOrder.LITTLE_ENDIAN).getShort();
    }

    public static void serializeString(com.pullenti.unisharp.Stream stream, String val, boolean deflate) throws java.io.IOException, Exception {
        if (val == null) {
            serializeInt(stream, -1);
            return;
        }
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(val)) {
            serializeInt(stream, 0);
            return;
        }
        byte[] data = encodeStringUtf8(val, false);
        if (deflate) 
            data = ArchiveHelper.compressGZip(data);
        serializeInt(stream, data.length);
        stream.write(data, 0, data.length);
    }

    public static String deserializeString(com.pullenti.unisharp.Stream stream, boolean deflate) throws java.io.IOException, Exception {
        int len = deserializeInt(stream);
        if (len < 0) 
            return null;
        if (len == 0) 
            return "";
        byte[] data = new byte[len];
        stream.read(data, 0, data.length);
        if (deflate) 
            data = ArchiveHelper.decompressGZip(data);
        return decodeStringUtf8(data, 0, -1);
    }

    /**
     * Прочитать байтовый массив из потока
     * @param s поток
     * @return результат
     */
    public static byte[] readStream(com.pullenti.unisharp.Stream s) throws java.io.IOException {
        if (s.canSeek()) {
            if (s.length() > (0L)) {
                byte[] res = new byte[(int)s.length()];
                s.read(res, 0, res.length);
                return res;
            }
        }
        byte[] buf = new byte[10000];
        com.pullenti.unisharp.MemoryStream mem = new com.pullenti.unisharp.MemoryStream();
        int k = 0;
        while (true) {
            int 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;
        }
        byte[] arr = mem.toByteArray();
        mem.close();
        return arr;
    }

    // Замена стандартной функции, которая очень тормозит
    public static boolean endsWith(String str, String substr) {
        if (str == null || substr == null) 
            return false;
        int i = str.length() - 1;
        int j = substr.length() - 1;
        if (j > i || (j < 0)) 
            return false;
        for (; j >= 0; j--,i--) {
            if (str.charAt(i) != substr.charAt(j)) 
                return false;
        }
        return true;
    }

    public static boolean endsWithEx(String str, String substr, String substr2, String substr3) {
        if (str == null) 
            return false;
        for (int k = 0; k < 3; k++) {
            if (k == 1) 
                substr = substr2;
            else if (k == 2) 
                substr = substr3;
            if (substr == null) 
                continue;
            int i = str.length() - 1;
            int j = substr.length() - 1;
            if (j > i || (j < 0)) 
                continue;
            for (; j >= 0; j--,i--) {
                if (str.charAt(i) != substr.charAt(j)) 
                    break;
            }
            if (j < 0) 
                return true;
        }
        return false;
    }

    /**
     * Закодировать строку кодировкой ASCII. Работает на всех платформах.
     * @param str строка
     * @return результат
     */
    public static byte[] encodeStringAscii(String str) {
        if (str == null) 
            return new byte[0];
        byte[] res = new byte[str.length()];
        for (int j = 0; j < str.length(); j++) {
            int i = (int)str.charAt(j);
            if (i < 0x100) 
                res[j] = (byte)i;
            else 
                res[j] = (byte)'?';
        }
        return res;
    }

    /**
     * Закодировать строку кодировкой windows-1251. Работает на всех платформах.
     * @param str строка
     * @return результат
     */
    public static byte[] encodeString1251(String str) {
        if (str == null) 
            return new byte[0];
        byte[] res = new byte[str.length()];
        for (int j = 0; j < str.length(); j++) {
            int i = (int)str.charAt(j);
            if (i < 0x80) 
                res[j] = (byte)i;
            else {
                byte b;
                com.pullenti.unisharp.Outargwrapper<Byte> wrapb662 = new com.pullenti.unisharp.Outargwrapper<Byte>();
                boolean inoutres663 = com.pullenti.unisharp.Utils.tryGetValue(m_utf_1251, i, wrapb662);
                b = (wrapb662.value != null ? wrapb662.value : 0);
                if (inoutres663) 
                    res[j] = b;
                else 
                    res[j] = (byte)'?';
            }
        }
        return res;
    }

    /**
     * Закодировать строку кодировкой windows-1252. Работает на всех платформах.
     * @param str строка
     * @return результат
     */
    public static byte[] encodeString1252(String str) {
        if (str == null) 
            return new byte[0];
        byte[] res = new byte[str.length()];
        for (int j = 0; j < str.length(); j++) {
            int i = (int)str.charAt(j);
            if ((i < 0x80) || i >= 0xA0) 
                res[j] = (byte)i;
            else {
                byte b;
                com.pullenti.unisharp.Outargwrapper<Byte> wrapb664 = new com.pullenti.unisharp.Outargwrapper<Byte>();
                boolean inoutres665 = com.pullenti.unisharp.Utils.tryGetValue(m_utf_1252, i, wrapb664);
                b = (wrapb664.value != null ? wrapb664.value : 0);
                if (inoutres665) 
                    res[j] = b;
                else 
                    res[j] = (byte)'?';
            }
        }
        return res;
    }

    /**
     * Декодировать строку из массива в кодировке Ascii. Работает на всех платформах.
     * @param dat байтовый масств
     * @param pos начальная позиция в массиве
     * @param len длина байт
     * @return строка с результатом
     */
    public static String decodeStringAscii(byte[] dat, int pos, int len) {
        if (dat == null) 
            return null;
        if (dat.length == 0) 
            return "";
        if (len < 0) 
            len = dat.length - pos;
        StringBuilder tmp = new StringBuilder();
        for (int j = pos; (j < (pos + len)) && (j < dat.length); j++) {
            int i = Byte.toUnsignedInt(dat[j]);
            if (i < 0x100) 
                tmp.append((char)i);
            else 
                tmp.append('?');
        }
        return tmp.toString();
    }

    /**
     * Декодировать строку из массива в кодировке windows-1251. Работает на всех платформах.
     * @param dat байтовый масств
     * @param pos начальная позиция в массиве
     * @param len длина байт
     * @return строка с результатом
     */
    public static String decodeString1251(byte[] dat, int pos, int len) {
        if (dat == null) 
            return null;
        if (dat.length == 0) 
            return "";
        if (len < 0) 
            len = dat.length - pos;
        StringBuilder tmp = new StringBuilder();
        for (int j = pos; (j < (pos + len)) && (j < dat.length); j++) {
            int i = Byte.toUnsignedInt(dat[j]);
            if (i < 0x80) 
                tmp.append((char)i);
            else if (m_1251_utf[i] == 0) 
                tmp.append('?');
            else 
                tmp.append((char)m_1251_utf[i]);
        }
        return tmp.toString();
    }

    /**
     * Декодировать строку из массива в кодировке windows-1252. Работает на всех платформах.
     * @param dat байтовый масств
     * @param pos начальная позиция в массиве
     * @param len длина байт
     * @return строка с результатом
     */
    public static String decodeString1252(byte[] dat, int pos, int len) {
        if (dat == null) 
            return null;
        if (dat.length == 0) 
            return "";
        if (len < 0) 
            len = dat.length - pos;
        StringBuilder tmp = new StringBuilder();
        for (int j = pos; (j < (pos + len)) && (j < dat.length); j++) {
            int i = Byte.toUnsignedInt(dat[j]);
            if ((i < 0x80) || i >= 0xA0) 
                tmp.append((char)i);
            else if (m_1252_utf[i] == 0) 
                tmp.append('?');
            else 
                tmp.append((char)m_1252_utf[i]);
        }
        return tmp.toString();
    }

    public static int[] m_1251_utf;

    public static int[] m_1252_utf;

    private static java.util.HashMap<Integer, Byte> m_utf_1251;

    private static java.util.HashMap<Integer, Byte> m_utf_1252;

    /**
     * Закодировать строку в коде UTF-8 с добавлением преамбулы
     * @param str кодируемая строка
     * @param addPreambl добавлять ли преамбулу EF BB BF
     * @return результирующий массив
     */
    public static byte[] encodeStringUtf8(String str, boolean addPreambl) {
        if (str == null) 
            return new byte[0];
        byte[] res = com.pullenti.unisharp.Utils.encodeCharset(java.nio.charset.Charset.forName("UTF-8"), str);
        if (!addPreambl) 
            return res;
        java.util.ArrayList<Byte> tmp = new java.util.ArrayList<Byte>(res.length + 3);
        com.pullenti.unisharp.Utils.addToArrayList(tmp, java.util.Arrays.asList(com.pullenti.unisharp.Utils.toBytesArrayObj(com.pullenti.unisharp.Utils.preambleCharset(java.nio.charset.Charset.forName("UTF-8")))));
        com.pullenti.unisharp.Utils.addToArrayList(tmp, java.util.Arrays.asList(com.pullenti.unisharp.Utils.toBytesArrayObj(res)));
        return com.pullenti.unisharp.Utils.toBytesArray(tmp.toArray(new Byte[tmp.size()]));
    }

    /**
     * Декодировать строку из UTF-8. Если есть преамбула, то она проигнорируется.
     * @param dat массив с закодированной строкой
     * @return раскодированная строка
     */
    public static String decodeStringUtf8(byte[] dat, int pos, int len) {
        if (dat == null) 
            return null;
        if (len < 0) 
            len = dat.length;
        if ((len > 3 && dat[pos] == ((byte)0xEF) && dat[pos + 1] == ((byte)0xBB)) && dat[pos + 2] == ((byte)0xBF)) {
            pos += 3;
            len -= 3;
        }
        return com.pullenti.unisharp.Utils.decodeCharset(java.nio.charset.Charset.forName("UTF-8"), dat, pos, len);
    }

    /**
     * Закодировать строку в 2-х байтовой кодировке Unicode, младший байт первый (UTF-16LE).
     * @param str кодируемая строка
     * @return результирующий массив
     */
    public static byte[] encodeStringUnicode(String str) {
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(str)) 
            return new byte[0];
        byte[] res = new byte[str.length() * 2];
        for (int i = 0; i < str.length(); i++) {
            int cod = (int)str.charAt(i);
            res[i * 2] = (byte)((cod & 0xFF));
            res[(i * 2) + 1] = (byte)((cod >> 8));
        }
        return res;
    }

    /**
     * Декодировать строку из 2-х байтовой кодировки Unicode, младший байт первый (UTF-16LE).
     * @param dat массив с закодированной строкой
     * @return раскодированная строка
     */
    public static String decodeStringUnicode(byte[] dat, int pos, int len) {
        if (dat == null) 
            return null;
        if (len < 0) 
            len = dat.length;
        StringBuilder res = new StringBuilder();
        for (int i = pos; (i < (pos + len)) && ((i + 1) < dat.length); i += 2) {
            int cod = Byte.toUnsignedInt(dat[i + 1]);
            cod <<= 8;
            cod |= (Byte.toUnsignedInt(dat[i]));
            res.append((char)cod);
        }
        return res.toString();
    }

    /**
     * Закодировать строку в 2-х байтовой кодировке Unicode, старший байт первый (UTF-16BE).
     * @param str кодируемая строка
     * @return результирующий массив
     */
    public static byte[] encodeStringUnicodeBE(String str) {
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(str)) 
            return new byte[0];
        byte[] res = new byte[str.length() * 2];
        for (int i = 0; i < str.length(); i++) {
            int cod = (int)str.charAt(i);
            res[(i * 2) + 1] = (byte)((cod & 0xFF));
            res[i * 2] = (byte)((cod >> 8));
        }
        return res;
    }

    /**
     * Декодировать строку из 2-х байтовой кодировки Unicode, старший байт первый (UTF-16BE).
     * @param dat массив с закодированной строкой
     * @return раскодированная строка
     */
    public static String decodeStringUnicodeBE(byte[] dat, int pos, int len) {
        if (dat == null) 
            return null;
        if (len < 0) 
            len = dat.length;
        StringBuilder res = new StringBuilder();
        for (int i = pos; i < (pos + len); i += 2) {
            int cod = Byte.toUnsignedInt(dat[i]);
            cod <<= 8;
            cod |= (Byte.toUnsignedInt(dat[i + 1]));
            res.append((char)cod);
        }
        return res.toString();
    }

    public static String correctCsvValue(String txt, boolean surroundBrackets) {
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(txt)) 
            return "";
        boolean sur = false;
        if (txt.indexOf('"') >= 0) {
            txt = txt.replace("\"", "\"\"");
            sur = true;
        }
        if (surroundBrackets && txt.indexOf(';') >= 0) 
            sur = true;
        if (surroundBrackets && sur) 
            return ("\"" + txt + "\"");
        else 
            return txt;
    }

    public static String correctJsonValue(String txt) {
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(txt)) 
            return "";
        StringBuilder res = new StringBuilder();
        if (txt != null) {
            for (char ch : txt.toCharArray()) {
                if (ch == '"') 
                    res.append("\\\"");
                else if (ch == '\\') 
                    res.append("\\\\");
                else if (ch == '/') 
                    res.append("\\/");
                else if (((int)ch) == 0xD) 
                    res.append("\\r");
                else if (((int)ch) == 0xA) 
                    res.append("\\n");
                else if (ch == '\t') 
                    res.append("\\t");
                else if (((int)ch) < 0x20) 
                    res.append(' ');
                else 
                    res.append(ch);
            }
        }
        return res.toString();
    }

    public static void correctXmlFile(StringBuilder res) {
        int i = res.toString().indexOf('>');
        if (i > 10 && res.charAt(1) == '?') 
            res.delete(0, 0 + i + 1);
        for (i = 0; i < res.length(); i++) {
            char ch = res.charAt(i);
            int cod = (int)ch;
            if ((cod < 0x80) && cod >= 0x20) 
                continue;
            if (cod >= 0x400 || (cod < 0x500)) 
                continue;
            res.delete(i, i + 1);
            res.insert(i, ("&#x" + String.format("%04X", cod) + ";"));
        }
    }

    /**
     * Сериализация объекта, реализующего IXmlReadWriteSupport, в байтовый массив. 
     * Работает одинаково на всех языках программирования.
     * @param obj объект
     * @return результат
     */
    public static byte[] serializeToBin(IXmlReadWriteSupport obj) throws Exception, javax.xml.stream.XMLStreamException, java.io.IOException {
        if (obj == null) 
            return null;
        StringBuilder res = new StringBuilder();
        try (com.pullenti.unisharp.XmlWriterWrapper xml = new com.pullenti.unisharp.XmlWriterWrapper(res, null)) {
            obj.writeToXml(xml, null);
        }
        correctXmlFile(res);
        byte[] dat = encodeStringUtf8(res.toString(), false);
        return ArchiveHelper.compressGZip(dat);
    }

    /**
     * Десериализация из байтового массива
     * @param dat массив, полученный функцией SerializeToBin
     * @param obj экземпляр десериализуемого объекта
     */
    public static void deserializeFromBin(byte[] dat, IXmlReadWriteSupport obj) throws javax.xml.stream.XMLStreamException, java.io.IOException, org.xml.sax.SAXException, Exception {
        if (dat == null) 
            return;
        byte[] data = ArchiveHelper.decompressGZip(dat);
        String str = decodeStringUtf8(data, 0, -1);
        com.pullenti.unisharp.XmlDocumentWrapper xml = new com.pullenti.unisharp.XmlDocumentWrapper();
        xml.doc = xml.db.parse(new org.xml.sax.InputSource(new java.io.StringReader(str)));
        obj.readFromXml(xml.doc.getDocumentElement());
    }

    public static byte[] serializeToBinList(java.util.ArrayList<IXmlReadWriteSupport> objs) throws Exception, javax.xml.stream.XMLStreamException, java.io.IOException {
        StringBuilder res = new StringBuilder();
        try (com.pullenti.unisharp.XmlWriterWrapper xml = new com.pullenti.unisharp.XmlWriterWrapper(res, null)) {
            xml.wr.writeStartElement("LIST");
            if (objs != null) {
                for (IXmlReadWriteSupport obj : objs) {
                    obj.writeToXml(xml, null);
                }
            }
            xml.wr.writeEndElement();
        }
        correctXmlFile(res);
        byte[] dat = encodeStringUtf8(res.toString(), false);
        return ArchiveHelper.compressGZip(dat);
    }

    public static int deserializeFromBinList(byte[] dat, java.util.ArrayList<IXmlReadWriteSupport> res) throws javax.xml.stream.XMLStreamException, java.io.IOException, org.xml.sax.SAXException, Exception {
        if (dat == null) 
            return 0;
        byte[] data = ArchiveHelper.decompressGZip(dat);
        String str = decodeStringUtf8(data, 0, -1);
        com.pullenti.unisharp.XmlDocumentWrapper xml = new com.pullenti.unisharp.XmlDocumentWrapper();
        xml.doc = xml.db.parse(new org.xml.sax.InputSource(new java.io.StringReader(str)));
        int i = 0;
        for (org.w3c.dom.Node x : (new com.pullenti.unisharp.XmlNodeListWrapper(xml.doc.getDocumentElement().getChildNodes())).arr) {
            if (res != null) {
                if (i >= res.size()) 
                    break;
                res.get(i).readFromXml(x);
            }
            i++;
        }
        return i;
    }

    /**
     * При сохранении значений в XML рекомендуется пропускать через эту функцию. 
     * Иначе если в строке окажутся некоторые символы (например, 0xC), то XML получается некорректным.
     * @param txt исходный текст узла или атрибута
     * @return откорректированный
     */
    public static String correctXmlValue(String txt) {
        if (txt == null) 
            return "";
        boolean corr = false;
        for (int i = 0; i < txt.length(); i++) {
            int cod = (int)txt.charAt(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 = (int)txt.charAt(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;
        StringBuilder tmp = new StringBuilder(txt);
        for (int i = 0; i < tmp.length(); i++) {
            char ch = tmp.charAt(i);
            if (((((int)ch) < 0x20) && ch != '\r' && ch != '\n') && ch != '\t') 
                tmp.setCharAt(i, ' ');
            else if (((int)ch) >= 0xD800 && ((int)ch) <= 0xDBFF) {
                if ((i + 1) >= tmp.length()) {
                    tmp.setCharAt(i, ' ');
                    break;
                }
                char ch1 = tmp.charAt(i + 1);
                if (((int)ch1) >= 0xDC00 && ((int)ch1) <= 0xDFFF) 
                    i++;
                else 
                    tmp.setCharAt(i, ' ');
            }
            else if (((int)ch) >= 0xDC00 && ((int)ch) <= 0xDFFF) 
                tmp.setCharAt(i, '?');
            else if (((int)ch) >= 0xFF00) 
                tmp.setCharAt(i, ' ');
        }
        return tmp.toString();
    }

    public static void correctHtmlChar(StringBuilder res, char c) {
        if (((((int)c) < 0x20) && c != '\r' && c != '\n') && c != '\t') 
            res.append(' ');
        else if (((int)c) > 0xF000) 
            res.append("&#").append(((int)c)).append(";");
        else if (((int)c) == 0xA || ((int)c) == 0xD) 
            res.append("\r\n<br/>");
        else if (c == ((char)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 
     */
    public static void correctHtmlValue(StringBuilder res, String txt, boolean isAttr, boolean isPureXml, boolean encodeAllNonansiChars) {
        if (txt == null) 
            return;
        if (com.pullenti.unisharp.Utils.stringsEq(txt, " ")) {
            res.append("&nbsp;");
            return;
        }
        for (int i = 0; i < txt.length(); i++) {
            char c = txt.charAt(i);
            if (((((int)c) < 0x20) && c != '\r' && c != '\n') && c != '\t') 
                res.append(' ');
            else if (((int)c) > 0xF000 || ((encodeAllNonansiChars && ((int)c) >= 0x80))) 
                res.append("&#").append(((int)c)).append(";");
            else if (((int)c) == 0xA || ((int)c) == 0xD) {
                if (i > 0 && ((int)txt.charAt(i - 1)) == 0xD) {
                }
                else if (isAttr || isPureXml) 
                    res.append("\r\n");
                else 
                    res.append("\r\n<br/>");
            }
            else if (c == ((char)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, если их нет
     */
    public static String[] getCommandlineArgumentsFromFile(String fname) throws Exception, java.io.IOException, InterruptedException {
        String txt = TextHelper.readStringFromFile(fname, false, false);
        java.util.ArrayList<String> args0 = new java.util.ArrayList<String>();
        if (txt != null) {
            for (int i = 0; i < txt.length(); i++) {
                if (!com.pullenti.unisharp.Utils.isWhitespace(txt.charAt(i))) {
                    int j;
                    if (txt.charAt(i) == '"') {
                        for (j = i + 1; j < txt.length(); j++) {
                            if (txt.charAt(j) == '"') 
                                break;
                        }
                        if (j < txt.length()) {
                            String val = txt.substring(i + 1, i + 1 + j - i - 1);
                            args0.add(val);
                            i = j;
                        }
                    }
                    else {
                        for (j = i + 1; j < txt.length(); j++) {
                            if (com.pullenti.unisharp.Utils.isWhitespace(txt.charAt(j))) 
                                break;
                        }
                        String val = txt.substring(i, i + j - i);
                        args0.add(val);
                        i = j;
                    }
                }
            }
        }
        if (args0.size() > 0) 
            return args0.toArray(new String[args0.size()]);
        return null;
    }

    /**
     * Извлечь все файлы из директории и всех поддиректорий
     * @param dir имя директории или файла
     * @return полные имена всех файлов
     */
    public static java.util.ArrayList<String> getAllFilesFromDirectory(String dir) {
        java.util.ArrayList<String> res = new java.util.ArrayList<String>();
        if (((new java.io.File(dir)).exists() && (new java.io.File(dir)).isFile())) 
            res.add(dir);
        else if (((new java.io.File(dir)).exists() && (new java.io.File(dir)).isDirectory())) 
            _getFiles(dir, res);
        return res;
    }

    private static void _getFiles(String path, java.util.ArrayList<String> res) {
        for (String f : com.pullenti.unisharp.Utils.getDirectoryItems(path, null, false)) {
            res.add(f);
        }
        for (String d : com.pullenti.unisharp.Utils.getDirectoryItems(path, null, true)) {
            _getFiles(d, res);
        }
    }

    /**
     * Очистить папку со всем содержимым
     * @param dir 
     * @param delRootDir удалять саму папку
     */
    public static void clearDirectory(String dir, boolean delRootDir) throws java.io.FileNotFoundException {
        if (!((new java.io.File(dir)).exists() && (new java.io.File(dir)).isDirectory())) 
            return;
        for (String f : getAllFilesFromDirectory(dir)) {
            (new java.io.File(f)).delete();
        }
        for (String d : com.pullenti.unisharp.Utils.getDirectoryItems(dir, null, true)) {
            clearDirectory(d, true);
        }
        if (delRootDir) 
            com.pullenti.unisharp.Utils.deleteDirectory(new java.io.File(dir));
    }

    /**
     * Корректировка XML, отправляемого по rest (для внутреннего использования)
     * @param res 
     * @return 
     */
    public static byte[] correctRestXml(StringBuilder res) {
        for (int 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 (!com.pullenti.unisharp.Utils.isWhitespace(res.charAt(i))) 
                        break;
                }
                if (i < res.length()) 
                    res.delete(0, 0 + i);
                break;
            }
        }
        String txt = res.toString();
        return encodeStringUtf8(txt, false);
    }

    /**
     * Это расширение функции Uri.UnescapeDataString, которая иногда раскодирует неправильно
     * @param val 
     * @return 
     */
    public static String unescapeDataString(String val) {
        if (com.pullenti.unisharp.Utils.isNullOrEmpty(val)) 
            return val;
        String str = val;
        StringBuilder tmp = new StringBuilder();
        byte[] buf = new byte[1];
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if (ch == '+') {
                tmp.append(' ');
                continue;
            }
            if (ch == '%' && ((i + 2) < str.length())) {
                int code = 0;
                for (int j = 0; j < 2; j++) {
                    ch = str.charAt(i + 1 + j);
                    if (Character.isDigit(ch)) 
                        code = (code * 16) + ((((int)ch) - ((int)'0')));
                    else if ("ABCDEF".indexOf(ch) >= 0) 
                        code = (code * 16) + 10 + "ABCDEF".indexOf(ch);
                }
                i += 2;
                buf[0] = (byte)code;
                String 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.charAt(i + 1) == '#') {
                int code = 0;
                i += 2;
                for (int j = i; j < str.length(); j++) {
                    if (!Character.isDigit(str.charAt(j))) 
                        break;
                    else {
                        i = j;
                        code = (code * 10) + ((((int)str.charAt(i)) - ((int)'0')));
                    }
                }
                tmp.append((char)code);
                i++;
            }
            else 
                tmp.append(ch);
        }
        return tmp.toString();
    }

    /**
     * Вычисление процента
     * @param val значение
     * @param max максимум
     * @return процент
     */
    public static int calcPercent(int val, int max) {
        if (val >= max) 
            return 100;
        if (val <= 0 || max <= 0) 
            return 0;
        if (max < 100000) 
            return (val * 100) / max;
        return val / ((max / 100));
    }
    public MiscHelper() {
    }
    
    static {
        m_1251_utf = new int[256];
        m_utf_1251 = new java.util.HashMap<Integer, Byte>();
        for (int i = 0; i < 0x80; i++) {
            m_1251_utf[i] = i;
        }
        int[] m_1251_80_BF = new int[] {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 (int i = 0; i < 0x40; i++) {
            m_1251_utf[i + 0x80] = m_1251_80_BF[i];
            m_utf_1251.put(m_1251_80_BF[i], (byte)((i + 0x80)));
        }
        for (int i = 0; i < 0x20; i++) {
            m_1251_utf[i + 0xC0] = ((int)'А') + i;
            m_utf_1251.put(((int)'А') + i, (byte)((i + 0xC0)));
        }
        for (int i = 0; i < 0x20; i++) {
            m_1251_utf[i + 0xE0] = ((int)'а') + i;
            m_utf_1251.put(((int)'а') + i, (byte)((i + 0xE0)));
        }
        int[] m_1252_80_9F = new int[] {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};
        m_1252_utf = new int[256];
        m_utf_1252 = new java.util.HashMap<Integer, Byte>();
        for (int i = 0; i < 0x100; i++) {
            m_1252_utf[i] = i;
        }
        for (int i = 0; i < 0x20; i++) {
            if (m_1252_80_9F[i] != 0) {
                m_1252_utf[i + 0x80] = m_1252_80_9F[i];
                m_utf_1252.put(m_1252_80_9F[i], (byte)((i + 0x80)));
            }
        }
    }
}
