Malicious Office (OLE) — malware analysis report

Static analysis result for SHA-256 9fd4044be3310122…

MALICIOUS

Office (OLE)

2.35 MB Created: 2018-08-07 11:32:34 Authoring application: AddinUpdater First seen: 2020-07-02
MD5: b011b3bef05bf97ca24ab2a292e97c70 SHA-1: e1bc2c9294ad53fcb5b6cc9809430961a82eb78f SHA-256: 9fd4044be3310122c04464015914c53931a53c3bd10cc2f22a0481ac08dc5c68
618 Risk Score

Malware Insights

MITRE ATT&CK
T1059.005 Visual Basic T1105 Ingress Tool Transfer T1204.002 Malicious File

The sample contains obfuscated VBA macros that leverage WScript.Shell and URLDownloadToFile to download and execute a second-stage payload. The Workbook_Open event is used to trigger this malicious behavior. The presence of CreateProcess and URLDownloadToFile API calls, along with WScript.Shell usage, strongly indicates a downloader or droppper functionality.

Heuristics 16

  • Reference to URLDownloadToFile API critical SC_STR_URLDOWNLOAD
    Reference to URLDownloadToFile API
  • VBA macros detected medium 10 related findings OLE_VBA_MACROS
    Document contains VBA macro code
  • Potential Shell call in VBA critical OLE_VBA_SHELL
    Potential Shell call in VBA
    Matched line in script
        On Error Resume Next: Dim Folder$, downloads_folder$, changed As Boolean, v
        Const USF$ = "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders\"
        downloads_folder$ = Replace(SETT.GetText("{374DE290-123F-4565-9164-39C4925E467B}", , USF$), "%USERPROFILE%", Environ("USERPROFILE"))
  • WScript.Shell usage critical OLE_VBA_WSCRIPT
    WScript.Shell usage
    Matched line in script
        HL$ = "http://ExcelVBA.ru/programmes/Unification/ReplaceTables"
        CreateObject("wscript.Shell").Run HL$
    End Sub
  • URLDownloadToFile in VBA critical OLE_VBA_DOWNLOAD
    URLDownloadToFile in VBA
    Matched line in script
    #If VBA7 Then        '  Office 2010-2013
        Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" _
                (ByVal pCaller As LongPtr, ByVal szURL As String, ByVal szFileName As String, _
  • Obfuscated auto-exec VBA loader critical OLE_VBA_OBFUSCATED_AUTOEXEC_LOADER
    Auto-exec VBA reconstructs strings with a heavy custom decoder (numeric char-array, repeated hex-string decode, or junk-token Replace removal) and feeds them to a COM-instantiation or execution sink. This obfuscated-loader shape keeps CreateObject/Shell/URL indicators out of the macro source.
    Matched line in script
        Dim xml As DOMDocument, node As IXMLDOMElement
        Set xml = CreateObject("MSXML.DOMDocument"): xml.async = False
        url_request = "http://www.cbr.ru/scripts/XML_daily.asp"    '?date_req=" + Format(RateDate, "dd\/mm\/yyyy")
  • CreateObject call high OLE_VBA_CREATEOBJ
    CreateObject call
    Matched line in script
        Dim xml As DOMDocument, node As IXMLDOMElement
        Set xml = CreateObject("MSXML.DOMDocument"): xml.async = False
        url_request = "http://www.cbr.ru/scripts/XML_daily.asp"    '?date_req=" + Format(RateDate, "dd\/mm\/yyyy")
  • GetObject call high OLE_VBA_GETOBJ
    GetObject call
    Matched line in script
    Function ClipboardText()
        With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
            .GetFromClipboard
  • VBA instantiates a COM class by raw CLSID high OLE_VBA_GETOBJECT_CLSID_EVASION
    VBA uses GetObject("new:{CLSID}") to instantiate a COM class by raw CLSID rather than a CreateObject ProgID — an uncommon bypass of name-based macro detection.
    Matched line in script
    Function ClipboardText()
        With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
            .GetFromClipboard
  • VBA p-code auto-exec with execution tokens high OLE_VBA_PCODE_AUTOEXEC_EXEC
    Compiled VBA/cache stream contains an auto-execution token together with shell/download/object-execution tokens. This catches p-code-only or source-extraction-failure macro documents where visible source is unavailable.
  • Workbook_Open macro low OLE_VBA_WBOPEN
    Workbook_Open macro
    Matched line in script
    Private Sub Workbook_Open()
        On Error Resume Next: Dim FirstRun As Boolean
  • Environ() call (env variable access) low OLE_VBA_ENVIRON
    Environ() call (env variable access)
    Matched line in script
    Function FILESETS_INFO_LOCAL_XML_PATH$()
        FILESETS_INFO_LOCAL_XML_PATH$ = Environ("TEMP") & "\" & PROJECT_NAME$ & "_" & FILESETS_XML_FILENAME$
    End Function
  • Reference to CreateProcess API high SC_STR_CREATEPROCESS
    Reference to CreateProcess API
  • Reference to Windows Script Host high SC_STR_WSCRIPT
    Reference to Windows Script Host
  • Suspicious extracted artifact high EXTRACTED_FILE_STATIC_TRIAGE
    One or more files extracted from inside this sample matched static suspicious-content checks such as script obfuscation, encoded payload blobs, packed data, or execution/download terms.
  • Embedded URL info EMBEDDED_URL
    One or more URLs were extracted from the document. The URL itself is not a detection — see the per-URL labels for which channel (macro, JS, link annotation, document body, ...) reached each URL.
    URL http://http://website.com/images/ Referenced by macro
    • http://ExcelVBA.ru/Referenced by macro
    • http://ExcelVBA.ru/paymentsReferenced by macro
    • https://ExcelVBA.ru/programmes/Unification/manualsReferenced by macro
    • https://ExcelVBA.ru/Referenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/outputReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/output/field/formulaReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/output/filenameReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/output/FileFormatReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/MergeRowsReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/output/fieldsReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/output/field/setupReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/output/template/addReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/output/templateReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/ReplaceTablesReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/fields/conditionsReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/fieldsReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/fields/calculation_orderReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/fields/sourceReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/filename_maskReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/XLS_CSVReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/fields/functionsReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/multi_sheetsReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/fields/replacementsReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/sheetReferenced by macro
    • https://excelvba.ru/programmes/Unification/manuals/config/fields/TradeMarkupReferenced by macro
    • http://ExcelVBA.ru/programmes/Unification/ReplaceTablesReferenced by macro
    • http://www.nncron.ru/help/RU/add_info/regexp.htmReferenced by macro
    • http://www.freevbcode.com/ShowCode.asp?ID=1593Referenced by macro
    • http://ExcelVBA.ru/php2/updates.phpldeReferenced by macro
    • http://ExcelVBA.ru/php2/updates.phpReferenced by macro
    • https://ExcelVBA.ru/�Referenced by macro
    • http://Excel-Automation.com/Referenced by macro
    • http://www.wordarticles.com/Shorts/RibbonVBA/RibbonVBADemo.phpReferenced by macro
    • http://excelvba.ru/Referenced by macro
    • http://website.com/images/123abc.jpgReferenced by macro
    • http://website.com/pictures/{filename}?from=MyWorkbookReferenced by macro
    • http://www.cbr.ru/scripts/XML_daily.aspReferenced by macro
    • http://translate.google.com/translate?sl=ru&tl=Referenced by macro

Extracted artifacts 1

Files carved from inside the sample during analysis.

FilenameKindSourceSize
macros.bas vba-macro oletools.olevba.extract_macros (decoded VBA source) 2725372 bytes
SHA-256: 469b523b3ec78e8dfa00ff7ce863f4d8afbc86f2d5f445758161d320de68de1a
Detection
ClamAV: No threats found
Obfuscation or payload: likely
Carved artifact contains 7 eval/decoder/string-building token(s). Carved artifact contains 6 long base64-like blob(s).
Preview script
First 1,000 lines of the extracted script
Attribute VB_Name = "ThisWB"
Attribute VB_Base = "0{00020819-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True
'---------------------------------------------------------------------------------------
' Module        : ThisWB                    Version:
' Author        : Igor Vakhnenko                   Date: 18.05.2016
' Professional application development for Microsoft Excel
' http://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Explicit

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    On Error Resume Next
    If Not Developer Then ThisWorkbook.Saved = True
    DeleteProgramCommandBar

    Application.OnKey "^."
    Application.OnKey "^,"
End Sub

Private Sub Workbook_Open()
    On Error Resume Next: Dim FirstRun As Boolean
    FirstRun = SETT.IsFirstRun
    If FirstRun Then ShowFirstRunForm
    If SetupCancelled Then
        Application.DisplayAlerts = False
        If TrueDeveloper Then MsgBox "Setup Cancelled", vbInformation Else ThisWorkbook.Close False
        Application.DisplayAlerts = True
        Exit Sub
    End If
    Enable_AccessVBOM_Macro_DataConnections        ' disables notifications
    SaveSetting PROJECT_NAME$, "Setup", "AddinPath", ThisWorkbook.FullName
    Application.OnKey "^r", "ReplacePictiresInSelectedRows"
    CreateProgramCommandBar 0

    Dim X
    X = SETTINGS_FOLDER$
    X = OUTPUT_FOLDER$
    X = PRICE_FOLDER$

    Application.OnKey "^.", "ShowNextWorkbook"
    Application.OnKey "^,", "ShowPrevWorkbook"
End Sub


Attribute VB_Name = "shsett"
Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True
'---------------------------------------------------------------------------------------
' VBA Document      : shsett
' Автор     : EducatedFool  (Игорь)                    Дата: 17.08.2012
' Разработка макросов для Excel, Word, CorelDRAW. Быстро, профессионально, недорого.
' http://ExcelVBA.ru/          ICQ: 5836318           Skype: ExcelVBA.ru
' Реквизиты для оплаты работы: http://ExcelVBA.ru/payments
'---------------------------------------------------------------------------------------

Dim CBR_RATES_LastUpdateTime As Date

Sub HideSheets_And_SaveAddin()
    On Error Resume Next: Err.Clear
    msg$ = "После нажатия ОК, листы файла программы будут скрыты," & vbNewLine & _
           "и файл программы будет сохранён." & vbNewLine & vbNewLine & _
           "Сохранение изменений может занять несколько секунд..."
    If MsgBox(msg, vbQuestion + vbOKCancel, "Сохранение настроек на листах программы") = vbCancel Then Exit Sub
    ThisWorkbook.IsAddin = True
    ThisWorkbook.Save
    Application.ScreenUpdating = True
    MsgBox "Изменения сохранены", vbInformation
End Sub

Sub UpdateAllRatesFromButton()
    If UpdateAllRates(True) Then
        MsgBox "Курсы валют успешно обновлены в " & Now, vbInformation, "Готово"
    Else
        MsgBox "При обновлении курсов валют с сайта ЦБ РФ произошла ошибка", vbExclamation, _
               "Невозможно обновить курсы валют"
    End If
End Sub

Function UpdateAllRates(Optional ByVal ForceUpdate As Boolean = False) As Boolean
    ' функция загружает курсы валют
    On Error Resume Next:    ' RateDate = Now
    If Not ForceUpdate Then If Now - CBR_RATES_LastUpdateTime < TimeSerial(5, 0, 0) Then Exit Function
    ' загружаем данные с ЦБ РФ
    Dim xml As DOMDocument, node As IXMLDOMElement
    Set xml = CreateObject("MSXML.DOMDocument"): xml.async = False
    url_request = "http://www.cbr.ru/scripts/XML_daily.asp"    '?date_req=" + Format(RateDate, "dd\/mm\/yyyy")
    If xml.Load(url_request) <> True Then Exit Function    ' Запрос к серверу ЦБР

    Dim ra As Range: Set ra = Me.Range(Me.[A2], Me.Range("A" & Me.Rows.Count).End(xlUp))

    For Each node In xml.SelectNodes("*/Valute")
        CurrencyName$ = node.SelectSingleNode("CharCode").Text
        CurrencyRate = CDbl(node.SelectSingleNode("Value").Text)
        divisor = Val(node.SelectSingleNode("Nominal").Text)
        'Debug.Print CurrencyName, CurrencyRate, divisor
        ra.Find(CurrencyName$, , xlValues, xlWhole).Next.Next = CurrencyRate / divisor
    Next

    UpdateAllRates = True: Err.Clear
    CBR_RATES_LastUpdateTime = Now
    ThisWorkbook.Saved = True
End Function

Attribute VB_Name = "Module_SplitCSV"
'---------------------------------------------------------------------------------------
' Module        : Module_SplitCSV
' Author        : EducatedFool                     Date: 11.08.2014
' Professional application development for Microsoft Excel
' http://ExcelVBA.ru/          ICQ: 5836318           Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Sub test_split_csv()
    On Error Resume Next
    filename$ = "D:\ПРОЕКТЫ\- Мои проекты - Excel\Unification\split CSV - test files\prays-69483.csv"
    filename$ = "D:\ПРОЕКТЫ\- Мои проекты - Excel\Unification\split CSV - test files\TOYOTA.txt"

    MaxRowsCount& = 50000

    Dim СписокИмёнФайлов As Collection
    Set СписокИмёнФайлов = SplitTextFile(filename$, "", MaxRowsCount&, vbNewLine, False)

    For Each Файл In СписокИмёнФайлов
        Debug.Print "Создан файл: " & Файл
    Next
End Sub


Function SplitTextFile(ByVal filename$, Optional ByVal DestFolder$, Optional ByVal MaxRowsCount& = 10000, _
                       Optional ByVal Delimiter$ = vbNewLine, Optional ByVal DeleteSourceFile As Boolean = True) As Collection
    ' Функция предназначена для разбивки текстового файла filename$ на несколько файлов
    ' меньшего размера - в каждом из которых будет не более MaxRowsCount& строк
    ' Разделение строк выполняется с использованием разделителя Delimiter$
    ' Создаваемые в папке DestFolder$ файлы получают имена вида part000001.filename.txt, part000002.filename.txt и т.д.
    ' если путь к папке DestFolder$ не задан - файлы создаются в папке с исходным файлом
    ' Возвращает коллекцию имён созданных файлов

    Dim source_filename$, source_folder$, InputFN As Integer, OutputFN As Integer, txt$, rc&, FilePart&
    Set SplitTextFile = New Collection

    source_filename$ = Dir(filename$, vbNormal)
    If source_filename$ = "" Then Exit Function

    source_folder$ = Left(filename$, Len(filename$) - Len(source_filename$))
    If DestFolder$ = "" Then DestFolder$ = source_folder$

    '    Debug.Print source_filename$
    '    Debug.Print source_folder$

    InputFN = FreeFile
    Open filename$ For Input As #InputFN

    FilePart& = FilePart& + 1: OutputFN = FreeFile
    PartFilename$ = DestFolder$ & "part" & Format(FilePart&, "000000") & "." & source_filename$
    SplitTextFile.Add PartFilename$
    Open PartFilename$ For Output As #OutputFN

    Do Until EOF(InputFN)
        DoEvents
        rc& = rc& + 1: Line Input #InputFN, txt$
        If rc& > MaxRowsCount& Then
            Close #OutputFN
            FilePart& = FilePart& + 1: OutputFN = FreeFile
            PartFilename$ = DestFolder$ & "part" & Format(FilePart&, "000000") & "." & source_filename$
            SplitTextFile.Add PartFilename$
            Open PartFilename$ For Output As #OutputFN
        End If
        Print #OutputFN, txt$
    Loop

    Close #InputFN
    Close #OutputFN




    Exit Function
    ext$ = "." & Split(filename$, ".")(UBound(Split(filename$, ".")))
    Set FSO = CreateObject("scripting.filesystemobject")
    Set ts = FSO.OpenTextFile(filename, 1, True): txt = ts.ReadAll: ts.Close

    HeaderRow$ = Split(txt, Delimiter$, 2)(0) & Delimiter$        ' берем первую строку из файла как заголовок
    txt = Split(txt, Delimiter$, 2)(1)        ' остаток текста - без строки заголовка

    ' удаляем разделители строк в конце текстовой строки (если таковые присутствуют)
    While txt Like "*" & Delimiter$: txt = Left(txt, Len(txt) - Len(Delimiter$)): Wend

    ' RowsCount = UBound(Split(txt, Delimiter$)) + 1    ' количество текстовых строк в файле
    FileIndex& = 1        ' индекс очередного создаваемого файла

    arr = Split(txt, Delimiter$): rc = 0: Set SplitTextFile = New Collection
    For i = LBound(arr) To UBound(arr)
        rc = rc + 1
        newtxt$ = newtxt$ & arr(i) & Delimiter$
        If rc >= MaxRowsCount& Or i = UBound(arr) Then        ' набрали достаточно строк для записи в файл
            NewFilename$ = Mid(filename$, 1, Len(filename$) - Len(ext$)) & "(" & FileIndex & ")" & ext$
            Set ts = FSO.CreateTextFile(NewFilename$, True)
            ts.Write HeaderRow$ & newtxt$: ts.Close
            SplitTextFile.Add NewFilename$
            FileIndex& = FileIndex& + 1
            rc = 0: newtxt$ = ""
        End If
    Next i
    Set ts = Nothing: Set FSO = Nothing
    If DeleteSourceFile Then Kill filename$        ' удаляем исходный файл, если DeleteSourceFile = TRUE
End Function


Attribute VB_Name = "F_Progress"
Attribute VB_Base = "0{B7642387-E441-4625-828D-83A2BC2EC703}{8493CFAA-0D50-40F9-9915-0BB6C7AB2D0E}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False

'---------------------------------------------------------------------------------------
' Module        : F_Progress                       Version: 2.2 (кроме программы Parser!)
' Author        : Igor Vakhnenko                   Date: 21.07.2015
' Professional application development for Microsoft Excel
' http://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Explicit
Public Indicator As ProgressIndicator, ButtonMacro$
Dim MSG_StopMacro$, MSG_StopMacroTitle$

Private Sub UserForm_Initialize()
    On Error Resume Next
    MSG_StopMacro$ = Run(TWN & "tt", "PI_MSG_StopMacro")
    If MSG_StopMacro$ = "" Then MSG_StopMacro$ = "Do you really want to stop the macro?"
    MSG_StopMacroTitle$ = Run(TWN & "tt", "PI_MSG_StopMacroTitle")
    If MSG_StopMacroTitle$ = "" Then MSG_StopMacroTitle$ = "Processing is not complete yet"
    Err.Clear
End Sub

Private Sub CommandButton_RunMacro_Click()
    On Error Resume Next
    If Len(ButtonMacro$) Then Run TWN & ButtonMacro$
End Sub

Private Sub CommandButton_stop_Click()
    On Error Resume Next
    If StopMacro Then        ' macro finished
        End
    Else        ' macro is running
        If MsgBox(MSG_StopMacro$, vbQuestion + vbDefaultButton2 + vbYesNo, MSG_StopMacroTitle$) = vbYes Then
            StopMacro = True
        End If
    End If
End Sub

Private Sub SpinButton_log_Change()
    On Error Resume Next: Dim n&
    n = Me.SpinButton_log.value
    Me.Height = IIf(n = 0, 82, 92 + n * 40)
    Me.TextBox_Log.Height = 40 * n
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    On Error Resume Next
    If Not Indicator Is Nothing Then Indicator.QueryClose
End Sub

Attribute VB_Name = "FieldCurrencyConverter"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
'---------------------------------------------------------------------------------------
' Class Module      : FieldCurrencyConverter
' Author        : EducatedFool                     Date: 01.03.2013
' Professional application development for Microsoft Excel
' http://ExcelVBA.ru/          ICQ: 5836318           Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Public Enabled As Boolean

Public CurrencyFrom As String    ' из какой валюты конвертировать
Public CurrencyTo As String    ' в какую валюту конвертировать

Public CurrencyDefault As String    ' валюта прайса по-умолчанию
'Public RateSource As String    ' источник котировки (сайт, настройки программы, поле прайса, и т.д.)


Public DetectCurrency As Boolean      ' распознавать тип валюты
Public DetectAs As String    ' распознавать валюту как
Public DetectConditions As FieldConditions    ' условия формата ячейки для распознавания валюты как DetectAs

Private Sub Class_Initialize()
    'RateSource = "Настройки"
End Sub

Function ExportToXML(Optional ByVal SaveDisabledConditions As Boolean = False) As IXMLDOMElement
    On Error Resume Next
    Dim xml As DOMDocument, node As IXMLDOMElement
    Set xml = CreateObject("Microsoft.XMLDOM")

    With xml
        .preserveWhiteSpace = True
        Set node = .appendChild(.createElement("CurrencyConverter"))
        node.Attributes.setNamedItem(.createAttribute("Enabled")).Text = Abs(CInt(Me.Enabled))

        node.appendChild(.createElement("CurrencyFrom")).Text = Me.CurrencyFrom
        node.appendChild(.createElement("CurrencyTo")).Text = Me.CurrencyTo
        node.appendChild(.createElement("CurrencyDefault")).Text = Me.CurrencyDefault
        'node.appendChild(.createElement("RateSource")).Text = Me.RateSource

        node.appendChild(.createElement("DetectCurrency")).Text = Abs(CInt(Me.DetectCurrency))
        node.appendChild(.createElement("DetectAs")).Text = Me.DetectAs
        node.appendChild Me.DetectConditions.ExportToXML
    End With

    Set ExportToXML = node
End Function

Sub LoadFromXML(ByVal node As IXMLDOMElement)
    On Error Resume Next
    Me.Enabled = False

    If Not node Is Nothing Then
        If node.BaseName <> "CurrencyConverter" Then
            MsgBox "Не удаётся загрузить настройки обработки стоимости", vbCritical, _
                   "node.baseName <> ""CurrencyConverter"""
            End
        End If

        Me.Enabled = CBool(Val(node.Attributes.getNamedItem("Enabled").Text))

        Me.CurrencyFrom = node.SelectSingleNode("CurrencyFrom").Text
        Me.CurrencyTo = node.SelectSingleNode("CurrencyTo").Text
        Me.CurrencyDefault = node.SelectSingleNode("CurrencyDefault").Text
        'Me.RateSource = node.SelectSingleNode("RateSource").Text

        Me.DetectCurrency = CBool(Val(node.SelectSingleNode("DetectCurrency").Text))
        Me.DetectAs = node.SelectSingleNode("DetectAs").Text

        Set Me.DetectConditions = New FieldConditions
        Me.DetectConditions.LoadFromXML node.SelectSingleNode("DetectConditions")
    End If
End Sub




Attribute VB_Name = "PriceList"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
Public config As PriceConfig
Public SheetConfig As PriceSheetConfig
Public filename As String
Public Values As Variant

Public IsSummaryPrice As Boolean

Function RowsCount() As Long
    On Error Resume Next
    RowsCount = UBound(Values)
End Function

Function Output(Optional ByRef OC As OutputConfig, Optional ByRef piOUT As ProgressIndicator)
    On Error Resume Next
    DoEvents
    Application.ScreenUpdating = True
    Dim pi As ProgressIndicator
    Set pi = piOUT.AddChildIndicator("Вывод прайс-листа в заданном формате ...", sss)
    pi.CancelButton.Visible = False
    Application.ScreenUpdating = False

    If OC Is Nothing Then Set OC = config.CreateDefaultOutputConfig
    pi.Line3 = "Конфигурация вывода: " & OC.Name
    If Me.IsSummaryPrice Then filename$ = "test.xls"
    'Stop
    'MsgBox "Количество записей в создаваемом прайс-листе: " & UBound(Values), vbInformation, Dir(filename)
    ' Debug.Print 0, "размер масcива", UBound(Me.Values)

    'arr = ProcessPriceArray(Me, OC, piOUT)
    ProcessPriceArray Me, OC, piOUT
    ' Debug.Print 1, "размер масcива", UBound(Me.Values)

    ' форматирование значений массива (числа, текст, даты), применение таблиц замены (RTable)
    arr = Me.Values
    FormatOutputArray arr, OC, piOUT
    ' Debug.Print 2, "размер масcива", UBound(Me.Values)
    Me.Values = arr

    OC.GroupAndSort Me

    DoEvents
    Select Case OC.Destination
        Case "CSV"
            pi.Parent.Line2 = "Идёт экспорт загруженных данных в файл формата CSV ..."
            filename$ = ExportPrice2CSV(Me, OC, pi)
        Case "XLS"
            pi.Parent.Line2 = "Идёт экспорт загруженных данных в файл Excel ..."
            filename$ = ExportPrice2XLS(Me, OC, pi)
        Case "Sheet"
    End Select
    If filename$ = "" Then GoTo ExitLabel

    pi.Parent.Line2 = "Создание итогового прайс-листа завершено"
    pi.Parent.Line3 = "Выполнение дополнительных операций с созданным файлом ..."


    Set OC.Actions.Parent = OC
    OC.Actions.ExecuteAll filename$, Me, pi    ' выполняем дополнительные действия с файлом
ExitLabel:
    pi.Hide
    Application.ScreenUpdating = True
End Function



'Function OutputFilename() As String
'    On Error Resume Next
'    'OutputFilename = SheetConfig.Codename
'    'If OutputFilename = "" Then OutputFilename = config.Codename
'    shortFilename$ = Split(Me.filename, "\")(UBound(Split(Me.filename, "\")))
'    If OutputFilename$ = "" Then OutputFilename$ = shortFilename$
'
'    OutputFilename = OutputFilename & " - sheet" & SheetConfig.Index
'End Function

Attribute VB_Name = "PriceField"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
Public Parent As PriceFields

Public Name As String
Public Index As Long
Public Description As String
Public Enabled As Boolean        ' FALSE - отключено, TRUE - задействовано
Public Required As Long        ' 0 - не обязательное, 1 - обязательное

Public SheetColumn As Long
Public DefaultValue As Variant
Public ValueMode As Long        ' 0 - значение, 1 - формула
Public Formula As String
Public CharsToRemove As String
'public Format as String

Public ReplaceTableName As String
Public MarginSetName As String

Public Functions As New FieldFunctions
Public Conditions As New FieldConditions
Public CostOptions As New FieldCostOptions
Public CurrencyConverter As New FieldCurrencyConverter

Public KeepValue As Long        ' 0 - отключено, 1 - задействовано
Public KeepLevel As Long        ' уровень категории (от 1 до 9). если 0, то не используется
Public ExcelFormulaR1C1 As String

Public UseCellText As Long ' 0 - берется значение ячейки (по-умолчанию так), 1 - берется отображаемый текст


Function IsStaticFormula() As Boolean
    On Error Resume Next: Err.Clear
    IsStaticFormula = (Me.ValueMode = 1) And (InStr(1, Me.Formula, "{") = 0)
End Function

Function RTable(Optional ByVal Reload As Boolean = False) As ReplaceTable
    On Error Resume Next
    Static RT As ReplaceTable
    If ReplaceTableName = "" Then Set RTable = New ReplaceTable: Exit Function

    If (Not RT Is Nothing) And Not Reload Then Set RTable = RT: Exit Function
    Set RTable = All_RT.GetItemByName(ReplaceTableName)
    Set RT = RTable
End Function

Private Sub Class_Initialize()
    Me.SheetColumn = 1
    Me.ValueMode = 0
End Sub

' ================== для работы со списком полей конфигурации ===============

Function MoveUp() As PriceField
    On Error Resume Next
    If Parent Is Nothing Then MsgBox "Ошибка перемещения полей", vbCritical, "Функция MoveUp": Exit Function
    If Me.Index <= 1 Then Exit Function

    Set MoveUp = Me
    Err.Clear: Parent.Items.Add MoveUp, , Me.Index - 1
    If Err = 0 Then res = Parent.DeleteField(Me.Index + 1)
End Function

Function MoveDown() As PriceField
    On Error Resume Next
    If Parent Is Nothing Then MsgBox "Ошибка перемещения полей", vbCritical, "Функция MoveDown": Exit Function
    If Parent.Items.Count <= Me.Index Then Exit Function

    Parent.GetFieldByIndex(Me.Index + 1).MoveUp        ' странный код, но работает. по-другому сделать не получилось
    Set MoveDown = Parent.GetFieldByIndex(Me.Index)
End Function



' ===========================================================================


Function ExportToXML(Optional ByVal FullExport As Boolean = True) As IXMLDOMElement
    Dim xml As DOMDocument, fieldnode As IXMLDOMElement, FF As IXMLDOMElement
    Set xml = CreateObject("Microsoft.XMLDOM")

    With xml
        .preserveWhiteSpace = True
        Set fieldnode = .appendChild(.createElement("Field"))
        fieldnode.Attributes.setNamedItem(.createAttribute("index")).Text = Me.Index
        If FullExport Then
            If Me.Enabled Then fieldnode.Attributes.setNamedItem(.createAttribute("Enable")).Text = Abs(CInt(Me.Enabled))
        End If

        fieldnode.appendChild(.createElement("Name")).Text = Me.Name
        If Len(Me.Description) Then _
           fieldnode.appendChild(.createElement("Description")).Text = Me.Description

        If FullExport Then
            fieldnode.appendChild(.createElement("SheetColumn")).Text = Me.SheetColumn
            If Me.UseCellText Then _
               fieldnode.appendChild(.createElement("UseCellText")).Text = Me.UseCellText
            If Me.Required Then _
               fieldnode.appendChild(.createElement("Required")).Text = Me.Required
            If Me.DefaultValue <> "" Then _
               fieldnode.appendChild(.createElement("DefaultValue")).Text = Me.DefaultValue
            fieldnode.appendChild(.createElement("ValueMode")).Text = Me.ValueMode
            If Len(Me.Formula) Then _
               fieldnode.appendChild(.createElement("Formula")).Text = Me.Formula
            If Len(Me.ExcelFormulaR1C1) Then _
               fieldnode.appendChild(.createElement("ExcelFormulaR1C1")).Text = Me.ExcelFormulaR1C1
            If Len(Me.CharsToRemove) Then _
               fieldnode.appendChild(.createElement("CharsToRemove")).Text = Me.CharsToRemove
            fieldnode.appendChild(.createElement("KeepValue")).Text = Me.KeepValue
            fieldnode.appendChild(.createElement("KeepLevel")).Text = Me.KeepLevel

            fieldnode.appendChild Me.Functions.ExportToXML
            fieldnode.appendChild Me.Conditions.ExportToXML
            If Me.CostOptions.Enabled Then
                With fieldnode.appendChild(Me.CostOptions.ExportToXML)
                    If Me.CurrencyConverter.Enabled Then
                        .appendChild Me.CurrencyConverter.ExportToXML
                    End If
                End With
            End If

            If Len(Me.ReplaceTableName) Then fieldnode.appendChild(.createElement("ReplaceTableName")).Text = Me.ReplaceTableName
            If Len(Me.MarginSetName) Then fieldnode.appendChild(.createElement("MarginSetName")).Text = Me.MarginSetName
        End If
    End With
    Set ExportToXML = fieldnode
End Function

Sub LoadFromXML(ByVal fieldnode As IXMLDOMElement)
    On Error Resume Next
    If fieldnode.BaseName <> "Field" Then
        MsgBox "Не удаётся загрузить настройки полей", vbCritical, _
               "fieldnode.baseName <> ""Field"""
        End
    End If


    Me.Name = fieldnode.SelectSingleNode("Name").Text
    Me.Index = Val(fieldnode.Attributes.getNamedItem("index").Text)
    Me.Enabled = CBool(Val(fieldnode.Attributes.getNamedItem("Enable").Text))

    Me.Description = fieldnode.SelectSingleNode("Description").Text

    Me.SheetColumn = Val(fieldnode.SelectSingleNode("SheetColumn").Text)
    If Me.SheetColumn < 1 Then Me.SheetColumn = 1

    Me.UseCellText = Val(fieldnode.SelectSingleNode("UseCellText").Text)
    Me.Required = Val(fieldnode.SelectSingleNode("Required").Text)

    Me.DefaultValue = fieldnode.SelectSingleNode("DefaultValue").Text
    Me.ValueMode = Val(fieldnode.SelectSingleNode("ValueMode").Text)

    Me.Formula = fieldnode.SelectSingleNode("Formula").Text
    Me.ExcelFormulaR1C1 = fieldnode.SelectSingleNode("ExcelFormulaR1C1").Text
    Me.CharsToRemove = fieldnode.SelectSingleNode("CharsToRemove").Text

    Me.KeepValue = Val(fieldnode.SelectSingleNode("KeepValue").Text)
    Me.KeepLevel = Val(fieldnode.SelectSingleNode("KeepLevel").Text)

    Me.ReplaceTableName = fieldnode.SelectSingleNode("ReplaceTable").Attributes.getNamedItem("Name").Text
    Me.ReplaceTableName = fieldnode.SelectSingleNode("ReplaceTableName").Text
    Me.MarginSetName = fieldnode.SelectSingleNode("MarginSetName").Text


    Set Me.Conditions = New FieldConditions
    Me.Conditions.LoadFromXML fieldnode.SelectSingleNode("FieldConditions")

    Set Me.Functions = New FieldFunctions
    Me.Functions.LoadFromXML fieldnode.SelectSingleNode("FieldFunctions")

    Set Me.CostOptions = New FieldCostOptions
    Me.CostOptions.LoadFromXML fieldnode.SelectSingleNode("FieldCostOptions")

    Set Me.CurrencyConverter = New FieldCurrencyConverter
    Me.CurrencyConverter.LoadFromXML fieldnode.SelectSingleNode("FieldCostOptions").SelectSingleNode("CurrencyConverter")
End Sub



Attribute VB_Name = "OutputField"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
'---------------------------------------------------------------------------------------
' Module        : OutputField
' Author        : Игорь                     Date: 22.06.2013
' Professional application development for Microsoft Excel
' http://ExcelVBA.ru/          ICQ: 5836318           Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Public Parent As OutputFields

Public Index As Long
'Public Enabled As Long    ' 0 - отключено, 1 - задействовано

Public Name As String
Public Description As String
Public Required As Long        ' 0 - не обязательное, 1 - обязательное

Public Caption As String
Public Format As String
Public DefaultValue As Variant

Public ValueMode As Long        ' 0 - значение, 1 - формула
Public Formula As String

Public ReplaceTableName As String

Public Color1 As Long, Color2 As Long        ' цвет заливки (Color1) и цвет текста (Color2) при группировке строк
Public ExcelFormulaR1C1 As String

' параметры объединения строк при выводе
Public MergeType As MERGE_FIELD_TYPES
Public MergeJoinSeparator As String
Public MergeRoundEnabled As Boolean        ' включен ли режим округления
Public MergeRoundDigitsAfterDecimal As Long        ' кол-во цифр после запятой
Public MergeRoundMode As Long        ' 0 - обычное округление, 1 - вверх, 2 - вниз


Function RTable(Optional ByVal Reload As Boolean = False) As ReplaceTable
    On Error Resume Next
    Static RT As ReplaceTable
    If ReplaceTableName = "" Then Set RTable = New ReplaceTable: Exit Function

    If (Not RT Is Nothing) And Not Reload Then Set RTable = RT: Exit Function
    Set RTable = All_RT.GetItemByName(ReplaceTableName)
    Set RT = RTable
End Function

Function MoveUp() As OutputField
    On Error Resume Next
    If Parent Is Nothing Then MsgBox "Ошибка перемещения полей", vbCritical, "Функция MoveUp": Exit Function
    If Me.Index <= 1 Then Exit Function

    Set MoveUp = Me
    Err.Clear: Parent.Items.Add MoveUp, , Me.Index - 1
    If Err = 0 Then res = Parent.DeleteField(Me.Index + 1)
End Function

Function MoveDown() As OutputField
    On Error Resume Next
    If Parent Is Nothing Then MsgBox "Ошибка перемещения полей", vbCritical, "Функция MoveDown": Exit Function
    If Parent.Items.Count <= Me.Index Then Exit Function

    Parent.GetFieldByIndex(Me.Index + 1).MoveUp        ' странный код, но работает. по-другому сделать не получилось
    Set MoveDown = Parent.GetFieldByIndex(Me.Index)
End Function

Private Sub Class_Initialize()
    Me.ValueMode = 0
    Me.Format = OutputFieldsFormats(0) & "&"
    Me.MergeJoinSeparator = ", "
    Me.MergeRoundDigitsAfterDecimal = 0
End Sub

Function ExportToXML() As IXMLDOMElement
    Dim xml As DOMDocument, fieldnode As IXMLDOMElement, ValueNode As IXMLDOMElement, FormatNode As IXMLDOMElement
    Set xml = CreateObject("Microsoft.XMLDOM")

    With xml
        .preserveWhiteSpace = True
        Set fieldnode = .appendChild(.createElement("OutputField"))
        fieldnode.Attributes.setNamedItem(.createAttribute("index")).Text = Me.Index
        fieldnode.Attributes.setNamedItem(.createAttribute("Name")).Text = Me.Name

        If Len(Me.Description) Then fieldnode.appendChild(.createElement("Description")).Text = Me.Description
        fieldnode.appendChild(.createElement("Caption")).Text = Me.Caption
        fieldnode.appendChild(.createElement("Required")).Text = Me.Required
        If Len(Me.ExcelFormulaR1C1) Then fieldnode.appendChild(.createElement("ExcelFormulaR1C1")).Text = Me.ExcelFormulaR1C1

        Set ValueNode = fieldnode.appendChild(.createElement("Value"))
        If Len(Me.DefaultValue) Then ValueNode.Attributes.setNamedItem(.createAttribute("Default")).Text = Me.DefaultValue
        ValueNode.Attributes.setNamedItem(.createAttribute("Format")).Text = Me.Format
        ValueNode.Attributes.setNamedItem(.createAttribute("ValueMode")).Text = Me.ValueMode
        If Len(Me.Formula) Then ValueNode.Attributes.setNamedItem(.createAttribute("Formula")).Text = Me.Formula

        If Len(Me.ReplaceTableName) Then fieldnode.appendChild(.createElement("ReplaceTableName")).Text = Me.ReplaceTableName

        With fieldnode.appendChild(xml.createElement("Merge"))
            .Attributes.setNamedItem(xml.createAttribute("Type")).Text = Me.MergeType
            .Attributes.setNamedItem(xml.createAttribute("JoinSeparator")).Text = Me.MergeJoinSeparator
            With .appendChild(xml.createElement("Round"))
                .Attributes.setNamedItem(xml.createAttribute("Enabled")).Text = Abs(CInt(Me.MergeRoundEnabled))
                .Attributes.setNamedItem(xml.createAttribute("DigitsAfterDecimal")).Text = Me.MergeRoundDigitsAfterDecimal
                .Attributes.setNamedItem(xml.createAttribute("Mode")).Text = Me.MergeRoundMode
            End With
        End With

    End With
    Set ExportToXML = fieldnode
End Function

Sub LoadFromXML(ByVal fieldnode As IXMLDOMElement)
    On Error Resume Next
    If fieldnode.BaseName <> "OutputField" Then
        MsgBox "Не удаётся загрузить настройки полей", vbCritical, "fieldnode.baseName <> «OutputField»"
        End
    End If

    Me.Index = Val(fieldnode.Attributes.getNamedItem("index").Text)
    Me.Name = fieldnode.Attributes.getNamedItem("Name").Text

    Me.Description = fieldnode.SelectSingleNode("Description").Text
    Me.Caption = fieldnode.SelectSingleNode("Caption").Text
    Me.ExcelFormulaR1C1 = fieldnode.SelectSingleNode("ExcelFormulaR1C1").Text

    Dim ValueNode As IXMLDOMElement
    Set ValueNode = fieldnode.SelectSingleNode("Value")

    Me.Required = Val(fieldnode.SelectSingleNode("Required").Text)
    Me.DefaultValue = ValueNode.Attributes.getNamedItem("Default").Text
    Me.Format = ValueNode.Attributes.getNamedItem("Format").Text
    Me.ValueMode = Val(ValueNode.Attributes.getNamedItem("ValueMode").Text)
    Me.Formula = ValueNode.Attributes.getNamedItem("Formula").Text

    With fieldnode.SelectSingleNode("Merge")
        Me.MergeType = Val(.Attributes.getNamedItem("Type").Text)
        Me.MergeJoinSeparator = .Attributes.getNamedItem("JoinSeparator").Text
        With .SelectSingleNode("Round")
            Me.MergeRoundEnabled = CBool(Val(.Attributes.getNamedItem("Enabled").Text))
            Me.MergeRoundDigitsAfterDecimal = Val(.Attributes.getNamedItem("DigitsAfterDecimal").Text)
            Me.MergeRoundMode = Val(.Attributes.getNamedItem("Mode").Text)
        End With
    End With

    Me.ReplaceTableName = fieldnode.SelectSingleNode("ReplaceTable").Attributes.getNamedItem("Name").Text
    Me.ReplaceTableName = fieldnode.SelectSingleNode("ReplaceTableName").Text
End Sub





Attribute VB_Name = "mod_OUTPUT_CONFIG_functions"
'---------------------------------------------------------------------------------------
' Module        : mod_OUTPUT_CONFIG_functions
' Author        : EducatedFool                     Date: 20.01.2013
' Professional application development for Microsoft Excel
' http://ExcelVBA.ru/          ICQ: 5836318           Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Compare Text
Option Private Module

Function GetDefaultOutputWorksheet() As Worksheet
    Application.ScreenUpdating = False: On Error Resume Next: Err.Clear
    Dim sh_template As Worksheet: Set sh_template = sht
    shtv = sh_template.Visible: sh_template.Visible = xlSheetVisible
    If Err Then MsgBox "Не удалось отобразить лист шаблона", vbCritical, "Ошибка в функции NewWorksheet ": End
    sh_template.Copy: DoEvents
    sh_template.Visible = shtv
    If Err Then MsgBox "Не удалось скопировать лист шаблона", vbCritical, "Ошибка в функции NewWorksheet ": End

    If ActiveWorkbook.Worksheets.Count > 1 Then MsgBox "2: Не удалось скопировать лист шаблона", vbCritical, "Ошибка в функции NewWorksheet ": End
    If ActiveWorkbook.Name = ThisWorkbook.Name Then MsgBox "3: Не удалось скопировать лист шаблона", vbCritical, "Ошибка в функции NewWorksheet ": End

    Set GetDefaultOutputWorksheet = ActiveWorkbook.Worksheets(1)
    If GetDefaultOutputWorksheet.Name <> sh_template.Name Then MsgBox "4: Не удалось скопировать лист шаблона", vbCritical, "Ошибка в функции NewWorksheet ": End
End Function

Function ExportPrice2CSV(ByRef price As PriceList, ByRef OC As OutputConfig, _
                         Optional ByRef piOUT As ProgressIndicator) As String
    ' формирует CSV файл из прайса price согласно конфигурации OC (OutputConfig)
    On Error Resume Next
    filename$ = OC.GetOutputFilename(price)

    piOUT.StartNewAction 5, 60, "Экспорт прайс-листа в CSV файл", "Обработка массива": piOUT.FP.Repaint
    DoEvents

    ' arr = ProcessPriceArray(price, OC, piOUT)
    arr = price.Values

    ' форматирование значений массива (числа, текст, даты), применение таблиц замены (RTable)
    ' FormatOutputArray arr, OC, piOUT

    piOUT.StartNewAction 60, 70, "Экспорт прайс-листа в CSV файл", "Удаление разделителей столбцов в значениях ...": piOUT.FP.Repaint
    DoEvents
    sepOld$ = OC.CSV_options.ColumnsSeparator
    sepNew$ = SeparatorReplacement(sepOld$)
    chr34$ = Chr(34): chr2_34$ = Chr(34) & Chr(34)

    Dim NeedBrakets As Boolean
    If OC.CSV_options.UseBrackets Then
        For i = LBound(arr) To UBound(arr)
            For j = LBound(arr, 2) To UBound(arr, 2)
                NeedBrakets = False
                If InStr(1, arr(i, j), chr34$) Then NeedBrakets = True: arr(i, j) = Replace(arr(i, j), chr34$, chr2_34$)
                If InStr(1, arr(i, j), " ") + InStr(1, arr(i, j), sepOld$) Then NeedBrakets = True
                If NeedBrakets Then
                    arr(i, j) = chr34$ & arr(i, j) & chr34$
                End If
                'arr(i, j) = Replace(arr(i, j), sepOld$, sepNew$)
            Next j
            If i Mod 500 = 0 Then DoEvents
        Next i
    End If

    piOUT.StartNewAction 70, 83, "Преобразование массива в CSV формат ...": piOUT.FP.Repaint

    txt$ = Arr2CSV(arr, OC.CSV_options.ColumnsSeparator, OC.CSV_options.RowsSeparator, piOUT)
    If OC.CSV_options.AddHeaderRow Then txt$ = OC.GetCSVHeader & txt$

    piOUT.StartNewAction 83, 85, "Запись в CSV файл ...": piOUT.FP.Repaint

    Application.DisplayAlerts = False
    If Not Workbooks(filename$) Is Nothing Then
        Workbooks(filename$).Close False
        SetAttr OUTPUT_FOLDER$ & filename, vbNormal
        Kill OUTPUT_FOLDER$ & filename
    End If
    Application.DisplayAlerts = True


    piOUT.StartNewAction 85, 100, "Перекодировка и сохранение CSV файла ...", "Подождите, это может занять много времени...": piOUT.FP.Repaint

    If Not SaveTextToFile(txt$, OUTPUT_FOLDER$ & filename, OC.CSV_options.encoding) Then
        Debug.Print Err.Description, Err.Number
        Debug.Print "Ошибка сохранения текста в файл " & vbNewLine & OUTPUT_FOLDER$ & filename
    End If

    '    With CreateObject("scripting.filesystemobject").CreateTextFile(OUTPUT_FOLDER$ & filename, True, True)
    '        .Write txt$: .Close
    '    End With
    '
    '    ChangeFileCharset OUTPUT_FOLDER$ & filename, OC.CSV_options.encoding

    ExportPrice2CSV = OUTPUT_FOLDER$ & filename$
End Function

Function FormatOutputArray(ByRef arr As Variant, ByRef OC As OutputConfig, Optional ByRef piOUT As ProgressIndicator)
    On Error Resume Next: Err.Clear
    ' форматирование значений массива (числа, текст, даты)
    Dim OF As OutputField, col&, RowsForDelete_Count&
    For Each OF In OC.Fields.Items
        piOUT.Line2 = "Форматирование и применение таблиц замены: поле «" & OF.Name & "»"
        col& = OF.Index
        DefValue = OF.DefaultValue

        If Not OF.RTable(True).IsEmpty Then
            With OF.RTable
                .PrepareRT_forQuickReplace
                For i = LBound(arr) To UBound(arr)
                    .ApplyTo arr(i, col&)
                Next i
            End With
        End If


        If OF.Required = 1 Then
            ErrValue$ = "%ErrValue%"
            lastCol& = UBound(arr, 2)
            RowsForDelete_Count& = 0

            piOUT.Line2 = "Поиск пустых и нулевых строк: поле «" & OF.Name & "»"
            For i = LBound(arr) To UBound(arr)
                If (arr(i, col&) = "") Or (arr(i, col&) = "0") Then
                    RowsForDelete_Count& = RowsForDelete_Count& + 1
                    arr(i, lastCol&) = ErrValue$
                End If
            Next i
            DoEvents

            piOUT.Line2 = "Формируем новый массив (без лишних строк): поле «" & OF.Name & "»"
            'Debug.Print "Строк до удаления неподходящих: " & UBound(arr)
            'Debug.Print "Строк после удаления неподходящих: " & UBound(arr) - RowsForDelete_Count&

            If UBound(arr) = RowsForDelete_Count& Then
                ReDim newarr(1 To 1, LBound(arr, 2) To UBound(arr, 2))
                newarr(1, 1) = "Все строки удалены, т.к. в «обязательном поле» «" & OF.Name & "» находились только пустые или нулевые значения"
                arr = newarr
                Exit Function
            End If

            If RowsForDelete_Count& Then
                ' формируем новый массив
                ReDim newarr(1 To UBound(arr) - RowsForDelete_Count&, LBound(arr, 2) To UBound(arr, 2))
                Dim newrow As Long
                For i = LBound(arr) To UBound(arr)
                    If arr(i, lastCol&) <> ErrValue$ Then
                        newrow = newrow + 1
                        For j = LBound(arr, 2) To UBound(arr, 2): newarr(newrow, j) = arr(i, j): Next j
                    End If
                    If i Mod 1000 = 0 Then DoEvents
                Next i

                arr = newarr
                Erase newarr
            End If
        End If


        FieldFormat$ = Split(OF.Format, "&", 2)(0)
        FieldFormatCustom$ = Split(OF.Format, "&", 2)(1)
        Select Case FieldFormat$

            Case "", "Текст"

                For i = LBound(arr) To UBound(arr)
                    If arr(i, col&) = "" Then arr(i, col&) = DefValue
                Next i
                If OC.Destination = "XLS" And FieldFormat$ = "Текст" Then        ' только для XLS
                    For i = LBound(arr) To UBound(arr)
                        If Left$(arr(i, col&), 1) <> "'" Then arr(i, col&) = "'" & arr(i, col&)
                    Next i
                End If


            Case "Число"

                For i = LBound(arr) To UBound(arr)
                    If Len(arr(i, col&)) > 0 Then
                        arr(i, col&) = Val(Replace(arr(i, col&), ",", "."))
                        If arr(i, col&) = 0 Then arr(i, col&) = Val(DefValue)
                        If Len(FieldFormatCustom$) Then arr(i, col&) = Replace(Format(arr(i, col&), FieldFormatCustom$), ",", ".")
                    Else
                        If Len(DefValue) Then arr(i, col&) = Val(DefValue)
                    End If
                Next i

            Case "Индекс"

                For i = LBound(arr) To UBound(arr): arr(i, col&) = i: Next i

            Case "Формула"

                For i = LBound(arr) To UBound(arr)
                    If arr(i, col&) = "" Then arr(i, col&) = DefValue
                Next i

                If OC.Destination = "XLS" Then        ' только для XLS
                    If ExcelFormulaIsCorrect(OF.ExcelFormulaR1C1) Then
                        ExcelFormula$ = OF.ExcelFormulaR1C1
                        For i = LBound(arr) To UBound(arr)
                            RenderExcelFormulaR1C1 ExcelFormula$, arr(i, col&)
                        Next i
                    End If
                End If

            Case "Дата"
                defaultDate = "": Err.Clear: If Len(Trim(DefValue)) Then defaultDate = CDate(DefValue)
                If Err Then defaultDate = ""

                For i = LBound(arr) To UBound(arr)
                    If IsDate(arr(i, col&)) Then
                        arr(i, col&) = CDate(arr(i, col&))
                    Else
…