// Модуль работы со схемами
// зависит от модуля Un_Progress
{uses
  'Un_VersionSupport';}

var
  // версия текущей загруженной схемы подпрограммой LoadSchema.
  vFormVer: string;

// загрузка схемы
procedure LoadSchema(NameSchema: String);
begin
  try

    Schema := CreateOleObject('Microsoft.XmlDom');
    if not Schema.load(NameSchema) then
      raise('Ошибка при загрузке схемы: ' + NameSchema);
    // загружаем номер версии схемы.
    LoadVersionSchema;
    
    except
      ExceptionLog( 'Сбой скрипта: Un_Schems.LoadSchema');
  end;
end; {End LoadSchema}

// загрузка схемы
function LoadSchemaEx( aFileName: String): Variant;
var
  xXml: Variant;
begin
  try

    xXml := CreateOleObject('Microsoft.XmlDom');
    if not xXml.load( aFileName)
      then raise('Ошибка при загрузке схемы: ' + aFileName);
    Result := xXml;
    
    except
      ExceptionLog( 'Сбой скрипта: Un_Schems.LoadSchemaEx');
  end;
  // загружаем номер версии схемы.
//  LoadVersionSchema;
end; {End LoadSchema}


{ загрузка номера версии схемы.
  вызов этой подпрограммы добавлен в подпрограмму Un_Schems.LoadSchema,
  чтобы всегда иметь версию текущей загруженной схемы.}
procedure LoadVersionSchema;
begin
  try
    
    vFormVer := GetVersionSchemaEx( Schema);
    
    except
      ExceptionLog( 'Сбой скрипта: "Un_Schems.LoadVersionSchema.".');
  end;
end;


//возвращает Node xml согласно загруженной схеме
//Data - входящая xml AzkExchange
//Name - наименования Node в выходной xml.
function FillElementBySchema(Data: Variant; Name: String): Variant;
var
  i: Integer;
  Element, Value, Field, ClassDefValue: Variant;
begin

    try
      Field := Null;
      ClassDefValue := Null;
      Element := Data;
      while Element.NodeName <> Element.ownerDocument.documentElement.nodeName do
      begin
        if Element.getAttribute('DOCUMENTCLASS_ID') <> Null then
        begin
          Field := 'field_' + Element.getAttribute('DOCUMENTCLASS_ID');
          ClassDefValue := 'defval_'  + Element.getAttribute('DOCUMENTCLASS_ID');

          Break;
        end;
        Element := Element.ParentNode;
      end;
      Value := VarToStr(Schema.documentElement.selectSingleNode(Name).getAttribute('amount'));

      if Value <> '' then
      with TStringList.Create do
      try
        CommaText := Value;

        for i := 0 to Count - 1 do
        begin
          Value := Strings[i];

          Data.setAttribute(Value, Format('%0.2f', [StrToCurrDef(Data.getAttribute(Value),0)]));
        end;
      finally
        Free;
      end;

      Result := Schema.CreateElement(Name);
      with Schema.documentElement.selectSingleNode(Name) do
      for i := 1 to childNodes.length - 1 do
      begin
        Element := childNodes.item[i];
        Value := ClassDefValue;
        if Value <> Null then
          Value := Element.getAttribute(Value);

        if Value = Null then
        begin
          Value := Field;
          if Value <> Null then
            Value := Element.getAttribute(Value);
          if Value = Null then
            Value := Element.getAttribute('field');
          if Value <> Null then
            Value := Data.getAttribute(Value);
        end;
        if (Value = Null) or (Value = '') then
          Value := VarToStrDef(Element.getAttribute('default'), '');
        Result.setAttribute(Element.nodeName, Value);

      end;
      Result := Result.cloneNode(False);

      except
        ExceptionLog( 'Сбой Un_Schems.FillElementBySchema');
    end;

end; {End FillElementBySchema}

{~FillElementBySchemaEx
  возвращает Node xml согласно загруженной схеме aSchema.
  в отличие от функции FillElementBySchema эта функция работает с указанной
  схемой, переданной в параметре aSchema.
  Data - входящая xml AzkExchange
  Name - наименования Node в выходной xml.}
function FillElementBySchemaEx( var aSchema: Variant;
                                aName: String;
                                aData: Variant): Variant;
var
  i: Integer;
  Element, Value, Field, ClassDefValue: Variant;
begin
  try

    try
      Field := Null;
      ClassDefValue := Null;
      Element := aData;
      if NOT VarIsNull( Element) then
        while Element.NodeName <> Element.ownerDocument.documentElement.nodeName do
        begin
          if Element.getAttribute('DOCUMENTCLASS_ID') <> Null then
          begin
            Field := 'field_' + Element.getAttribute('DOCUMENTCLASS_ID');
            ClassDefValue := 'defval_'  + Element.getAttribute('DOCUMENTCLASS_ID');
            Break;
          end;
          Element := Element.ParentNode;
        end;

      Value := VarToStr(aSchema.documentElement.selectSingleNode(aName).getAttribute('amount'));
      if Value <> '' then
        with TStringList.Create do
          try
            CommaText := Value;
            for i := 0 to Count - 1 do begin
              Value := Strings[i];
              aData.setAttribute( Value,
                                  Format( '%0.2f',
                                         [StrToCurrDef(aData.getAttribute(Value), 0)]));
            end;//for//
            finally
              Free;
          end;

      Result := aSchema.CreateElement(aName);
      with aSchema.documentElement.selectSingleNode(aName) do
      for i := 1 to childNodes.length - 1 do begin
        Element := childNodes.item[i];
        Value := ClassDefValue;
        if Value <> Null then Value := Element.getAttribute(Value);
        if Value = Null then begin
          Value := Field;
          if Value <> Null then Value := Element.getAttribute(Value);
          if Value = Null then Value := Element.getAttribute('field');
          if Value <> Null then Value := aData.getAttribute(Value);
        end;
        if (Value = Null) or (Value = '')
          then Value := VarToStrDef(Element.getAttribute('default'), '');
        Result.setAttribute(Element.nodeName, Value);
      end;//for//
      Result := Result.cloneNode(False);
      except
        ExceptionLog( 'Сбой Un_Schems.FillElementBySchemaEx');
    end;
    finally
      
  end;
end; {End FillElementBySchema}


function ValidateFileFK( aFileNameFK, aFileNameSchema, aMsg1,aMsg2: String): boolean;
var
  xValidateErrors: TStringList;
  xValidateFileName: string;
begin

    try
      Result := TRUE;
      if NOT param_CtlValidOn then EXIT;

      WriteLog( FORMAT( 'Валидация файла %s...', [ aFileNameFK]), TRUE);

      xValidateErrors := TStringList.Create;
      try
        xValidateErrors.Text := ValidateFileFKbySchema(
                                     aFileNameFK,
                                     aFileNameSchema,
                                     DirIni + 'validate_template.xsl');
        if (LENGTH( xValidateErrors.Text) > 0) then begin
          xValidateFileName := ChangeFileExt( FileValidate, '.html');
          xValidateErrors.SaveToFile( xValidateFileName);
          WriteInfo( FORMAT( 'Файл %s не прошел валидацию. Ошибки описаны в файле: %s',
                              [ aFileNameFK,
                                xValidateFileName]));
          // открываем журнал ошибок
          ShellExecute( 'explorer', xValidateFileName, False);

          if (MessageDlg(
                FORMAT( 'Файл %s не прошел валидацию.'#13#10'%s',
                        [ aFileNameFK,
                          aMsg1]),
                mtConfirmation,
                mbYes + mbNo,
                0) = mrNo)
            then begin
              Result := FALSE;
              WriteInfo( aMsg2);
            end;
          EXIT;
        end;
        finally
          xValidateErrors.Free;
      end;
      except
        ExceptionProc( 'ValidateFileFK');
    end;

end;


function ValidateFileFK_Load( aFileNameFK, aFileNameSchema: String): boolean;
begin

    try
      Result := ValidateFileFK( aFileNameFK,
                                aFileNameSchema,
                                'Продолжить загрузку?',
                                'Загрузка прервана');
      except
        ExceptionProc( 'ValidateFileFK_Load');
    end;

end;


procedure FillElementBySchemaLoad(Doc, ConvXml: Variant);
var
  i: Integer;
  Field, Value: String;
  Element: Variant;
begin

    try
      with Schema.documentElement.selectSingleNode(ConvXml.nodeName) do
      for i := 1 to childNodes.length - 1 do
      begin
        Element := childNodes.item[i];
        Field := VarToStrDef(Element.getAttribute('field'), '');
        if Field <> '' then
        begin
          Value := ConvXml.getAttribute(Element.nodeName);
          if Value <> '' then
            Doc.setAttribute(Field, Value)
          else
            Doc.setAttribute(Field, '');
        end;
      end;

      except
        ExceptionLog( 'Сбой Un_Schems.FillElementBySchemaLoad');
    end;

end; {End FillElementBySchemaLoad}


{~DoSelectSingleNode
  эта функция надстройка над страндартным методом selectSingleNode в XmlNode.
  проблема только в том, что если узел не найден, возвращается непонятно
  что, что приводит к сбою выполнения скрипта.
  поэтому реализована отдельная функция которая ищет узел перебором
  и возвращает NULL если узел не найден.}
function DoSelectSingleNode( aXmlNode: variant; aNodeName: string): variant;
var
  i: integer;
begin

    try
      for i := 0 to aXmlNode.ChildNodes.Length - 1 do begin
        Result := aXmlNode.ChildNodes.Items[ i];
        if (Result.NodeName = aNodeName)
          then EXIT;
      end;//for//
      Result := NULL;
      except
        ExceptionProc( 'DoSelectSingleNode');
    end;

end;


//процедура валидации согласно атрибутов pattern схемы.
function ValidateFormat: Boolean;
var
  i: Integer;
  ValidateFile: TStringList;
  NumStrInFile: Integer;

  procedure ValidateInfo(ElementValidate: Variant);
  var
    j, arrayidx: Integer;
    Node: Variant;
    VarArray: array of Variant;
  begin
    ValidateFile.Add(ElementValidate.ChildNodes.Item[0].text);
    arrayidx := 0;
    for j := 1 to ElementValidate.ChildNodes.Length - 1 do
      if ElementValidate.ChildNodes.Item[j].NodeName = 'VALIDATE' then
        ValidateFile.Add(ElementValidate.childNodes.Item[j].ChildNodes.Item[0].Text)
      else begin
        inc(arrayidx);
        SetLength(VarArray, arrayidx);
        VarArray[arrayidx - 1] := j;
      end;
    if arrayidx <> 0 then
      for j := 0 to Length(VarArray) - 1 do
        ValidateInfo(ElementValidate.ChildNodes.Item[VarArray[j]]);
  end;

  {~ValidateElement
    разбор раздела Element (<ZR a1=".."....>) }
  procedure ValidateElement(Element: Variant);
  var
    i: Integer;
    childNode, parentNode, MatchCol, Match: Variant;
    xSchemaNode: Variant;
//    xSchemaElement: Variant;
    xSchemaNodeDetail: Variant;
    xValidateNode: Variant;
    xValidateElement: Variant;
    xValidateElementText: string;
  begin

      try
//        with Schema.documentElement.selectSingleNode(Element.nodeName) do begin
        xSchemaNode := DoSelectSingleNode( Schema.documentElement,
                                           Element.nodeName);


          // создается элемент проверки раздела Element
          xValidateElement := NULL;
          {
          xValidateElement := Validate.appendChild(
                                    Validate.ownerDocument.createElement(
                                                            Element.nodeName));
                                                            }
          // текст узла проверки перем из описания раздела схемы
          // например: ЭД ЗОР %DOC_NUMBER% %AMOUNT%
          xSchemaNodeDetail := DoSelectSingleNode( xSchemaNode, 'annotation');
//          xValidateElement.Text := xSchemaNodeDetail.text;
          xValidateElementText := xSchemaNodeDetail.text;
                  //xSchemaNode.selectSingleNode('annotation').text;

          { замена всех строк "%поле%" на значение поля из полей Element }
          RegExp.Pattern := '%[_,A-Z]{1,30}%';
          MatchCol := RegExp.Execute( xValidateElementText);

          for i := 0 to MatchCol.Count - 1 do begin
            Match := MatchCol.Item[i];
            xValidateElementText :=
                        StringReplace( xValidateElementText,
                                       Match.Value,
                                       Element.getAttribute(
                                              Copy( Match.Value,
                                                    2,
                                                    Length(Match.Value) - 2)));
          end;//for//

          // поиск в схеме описания раздела Element
{          xSchemaNode := Schema.documentElement.selectSingleNode(
                                                              Element.nodeName);}
//          with Schema.documentElement.selectSingleNode(Element.nodeName) do
//          begin
          // перебор всех полей раздела Element и проверка их по разделу схемы.
          // для каждого поля ищется подраздел в узле схемы
            for i := 0 to Element.attributes.length - 1 do begin

              // ищем подраздел в узле схемы для поля элемента
              xSchemaNodeDetail := DoSelectSingleNode(
                                        xSchemaNode,
                                        Element.attributes.items[i].nodeName);
//            with selectSingleNode(Element.attributes.items[i].nodeName) do
//            begin

              RegExp.Pattern := VarToStrDef(
                                    xSchemaNodeDetail.getAttribute('pattern'), '');
              // проверка значения поля элемента
              if (LENGTH( RegExp.Pattern) <= 0) then CONTINUE;

              if NOT RegExp.Test(Element.attributes.items[i].nodeValue) then begin

//                Validate := Validate.appendChild(Validate.ownerDocument.createElement('VALIDATE'));
                if VarIsNULL( xValidateElement) then begin
                  xValidateElement := Validate.appendChild(
                                    Validate.ownerDocument.createElement(
                                                            Element.nodeName));
                  xValidateElement.Text := xValidateElementText;
                end;

                xValidateNode := xValidateElement.appendChild(
                                      Validate.ownerDocument.createElement(
                                                                  'VALIDATE'));
                if (NumStrInFile <= 0)
                  then xValidateNode.text :=
                          FORMAT( 'Не верное значение параметра: %s <%s=%s> описание: %s',
                                  [ xSchemaNodeDetail.selectSingleNode('annotation').text,
                                    Element.attributes.items[i].nodeName,
                                    Element.attributes.items[i].nodeValue,
                                    xSchemaNodeDetail.selectSingleNode('pattern').text])
                  else xValidateNode.text :=
                          FORMAT( 'Не верное значение параметра (стр. № %s): %s <%s=%s> описание: %s',
                                  [ IntToStr(NumStrInFile),
                                    xSchemaNodeDetail.selectSingleNode('annotation').text,
                                    Element.attributes.items[i].nodeName,
                                    Element.attributes.items[i].nodeValue,
                                    xSchemaNodeDetail.selectSingleNode('pattern').text]);
//                Validate := Validate.parentNode;
                end;
            end;//for//


            for i := 0 to Element.childNodes.length - 1 do begin
              childNode := Element.childNodes.items[i];
              Inc(NumStrInFile);
              ValidateElement(childNode);
            end;//for//

            // если ошибок нет, то раздел валидации элемента Element удаляется

{
            if (xValidateElement.childNodes.length <= 0)
              then Validate.removeChild( xValidateElement);
              }
{
            parentNode := Validate.parentNode;
            if Validate.childNodes.length = 1 then
              Validate.parentNode.removeChild(Validate);
            Validate := parentNode;
            }

//          end;

//        end;
        except
          ExceptionLog( 'Сбой Un_Schems.ValidateFormat.ValidateElement');
      end;

  end;

begin

    try

      Validate := CreateOleObject('Microsoft.XmlDom');
      Validate := Validate.appendChild(Validate.createElement('VALIDATE'));
      Validate.Text := Schema.documentElement.selectSingleNode('annotation').Text;
      SetProgressBar( 0, Data.documentElement.childNodes.length);
      WriteLog('Валидация ...', True);
      NumStrInFile := -1;
      for i := 0 to Data.documentElement.childNodes.length - 1 do begin
        ShowProgress( i);
        NumStrInFile := i + 1;
        ValidateElement(Data.documentElement.childNodes.items[i]);
      end;
      ShowProgress( 0);
      Validate := Validate.ownerDocument;
      if Validate.documentElement.childNodes.length = 1 then
        Validate.documentElement.removeChild(Validate.documentElement.childNodes.items[0]);
      StatusBarSimpleText( '');
      Result := Validate.documentElement.childNodes.length = 0;
      {if not Result then
        Validate.loadXml('<?xml version="1.0" encoding="Windows-1251"?>' +
            FormatXmlData(Validate.documentElement.xml));}
      // Вывод пользователю результата валидации
      if not Result then
      begin
        WriteLog('Вывод результатов валидации в лог ...', True);
        ValidateFile := TStringList.Create;
        try
          ValidateFile.Add('Результат валидации:');
          SetProgressBarMax( Validate.documentElement.ChildNodes.Length);
          //класс XmlDomDocument считает элемент #text нодом, поэтому цикл с 1.
          for i := 1 to Validate.documentElement.ChildNodes.Length - 1 do
          begin
            ShowProgress(i + 1);
            ValidateFile.Add('');
            ValidateInfo(Validate.documentElement.ChildNodes.Item[i]);
          end;
          // логируем в детальном журнале результат проверки

          ValidateFile.SaveToFile(FileValidate);
        finally
          ValidateFile.Free;
        end;
      end;
      except
        ExceptionProc( 'Un_Schems.ValidateFormat');
    end;

end; {End ValidateFormat}


//сохранение сформировнной xml в стрктурировнный текстовый файл формата.
procedure XmlToTextFile(FileName: String);
var
  i: Integer;
  childNode: Variant;
  FileStream: TFileStream;
  procedure WriteElement(Element: Variant);
  var
    i: Integer;
    childNode: Variant;
  begin
    FileStream.Write(Element.nodeName, Length(Element.nodeName));
    FileStream.Write('|', 1);
    for i := 0 to Element.attributes.length - 1 do
    begin
      FileStream.Write(Element.attributes.item[i].nodeValue, Length(Element.attributes.item[i].nodeValue));
      FileStream.Write('|', 1);
    end;
    FileStream.Write(#13#10, 2);
    for i := 0 to Element.childNodes.length - 1 do
    begin
      childNode := Element.childNodes.items[i];
      WriteElement(childNode);
    end;
  end; {End WriteElement}
begin
  FileStream := TFileStream.Create(FileName, fmCreate);
  try
    for i := 0 to Data.documentElement.childNodes.length - 1 do
    begin
      childNode := Data.documentElement.childNodes.items[i];
      WriteElement(childNode);
    end;
  finally
    FileStream.Free;
  end;
end; {End XmlToTextFile}


{~TextFileToXml
  загрузка данных из файла FileName с разделителями в XML Data по схеме Schema.
  Data и Schema - глобальные переменные!
  реализовано через функцию Un_ToolsXml.TextFileToXmlEx. }
function TextFileToXml(FileName: String): boolean;
begin

    try
      Result := TextFileToXmlEx( FileName, Data, Schema);
      except
        ExceptionLog( 'Сбой Un_Schems.TextFileToXml');
    end;

end;
(*
var
  i, j: Integer;
  Value: TStringList;
  NodeSchema: Variant;
  SL: TStringList;
begin



    try
      WriteLog('Заполнение данных из файла: ' + FileName, True);
      Value := TStringList.Create;
      Value.Delimiter := '|';
      SL:= TStringList.Create;
      with SL do
      try
        Data := Data.documentElement;
        LoadFromFile(FileName);

        SetProgress( 0, Count);     // установка прогресс бара 0...count
        for i := 0 to Count - 1 do
        begin
          ShowProgress(i);     // прогресс
          Strings[i] := StringReplace(Strings[i], 'combo', '');
          Value.DelimitedText := StringReplace(StringReplace(Strings[i], ' ', 'combo'), '"', 'hooks');
          if (Value.Count - 1) > 0 then
          begin
            Value.Delete(Value.Count - 1);
            for j := 0 to Value.Count - 1 do
              Value[j] := StringReplace(StringReplace(Value[j], 'combo', ' '), 'hooks', '"');

            // по описанию XML возвращается коллекция элементов а не узел!
            // проверено на windows 7
            if VarIsNull( Schema.documentElement.getElementsByTagName(Value.Strings[0]))
              then NodeSchema := NULL
              else NodeSchema := Schema.documentElement.getElementsByTagName(Value.Strings[0]).Item[0];

            if VarIsNull( NodeSchema) OR (NodeSchema.Length = 0) then
              raise('В схеме не найден блок <' + Value.Strings[0] + '>');
            NodeSchema := NodeSchema.item[0];

            if (Value.Count <> NodeSchema.childNodes.length) then
              raise('В блоке <' + Value.Strings[0] +
                '> не совпадает количество полей описанных в схеме, в схеме (' +
                IntToStr(NodeSchema.childNodes.length - 1) + ') в файле (' + IntToStr(Value.Count) + ')');
            Data := Data.appendChild(Data.ownerDocument.createElement(Value.Strings[0]));

            for j := 1 to NodeSchema.childNodes.Length - 1 do
              Data.setAttribute(NodeSchema.childNodes.item[j].nodeName, Value.Strings[j]);

            Data := Data.parentNode;
          end;
        end;
      finally
        SL.Free;
        Value.Free;
        ShowProgress( 0);//ProgressBar.Position := 0;
        Data := Data.ownerDocument;
      end;
      except
        ExceptionLog( 'Сбой Un_Schems.TextFileToXml');
    end;

end; {End TextFileToXml}
*)


{~LineTextFileToXml
  Функция конвертит только одну строку в файле с указанным номером Line.
  Если количество строк в файле < LineNum, то возвращает null.

  exchange #2571
    выполнеа доработка:

    раньше, если были обнаружены ошибки при загрузке файла, возбуждалось
    исключение, приводящее к прерыванию работы скрипта.
    теперь, если будет ошибка - она будут сохранена в журнале info,
    и результат загрузки будет =NUL.}
function LineTextFileToXml(FileName: String; LineNum: Integer): Variant;
var
  j: Integer;
  Value: TStringList;
  NodeSchema: Variant;
begin

    try
      Result := Null;
      Value := TStringList.Create;
      Value.Delimiter := '|';
      with TStringList.Create do
      try
        LoadFromFile(FileName);

        if Count <= LineNum then Exit; // УФК прислал файл
        Result := CreateOleObject('Microsoft.XmlDom');
        Result := Result.appendChild(Result.createElement('FORMAT'));
        Value.DelimitedText := StringReplace(StringReplace(Strings[LineNum], ' ', 'combo'), '"', 'hooks');
        if (Value.Count - 1) > 0 then
        begin
          Value.Delete(Value.Count - 1);
          for j := 0 to Value.Count - 1 do
            Value[j] := StringReplace(StringReplace(Value[j], 'combo', ' '), 'hooks', '"');
          NodeSchema := Schema.documentElement.getElementsByTagName(Value.Strings[0]);
          if NodeSchema.Length = 0 then begin
            // exchange #2571
            // если обнаружена ошибка при загрузке файла, возвращаем результат
            // =NULL, в журнал пишем сообщение и завершаем загрузку.
            // раньше возбуждали исключение с прерыванием работы скрипта.
            WriteInfo( format( 'Ошибка загрузки файла %s. В схеме не найден блок <%s>',
                                [ FileName,
                                  Value.Strings[0]]));
            Result := NULL;
            EXIT;
          end;
          NodeSchema := NodeSchema.item[0];



          if Value.Count <> NodeSchema.childNodes.length then begin
            // exchange #2571
            // если обнаружена ошибка при загрузке файла, возвращаем результат
            // =NULL, в журнал пишем сообщение и завершаем загрузку.
            // раньше возбуждали исключение с прерыванием работы скрипта.
            WriteInfo( format( 'Ошибка загрузки файла %s. В блоке <%s> '
                                + 'не совпадает количество полей описанных в схеме, '
                                + 'в схеме (%s) в файле (%s)',
                               [ FileName,
                                 Value.Strings[0],
                                 IntToStr(NodeSchema.childNodes.length),
                                 IntToStr(Value.Count)]));
            Result := NULL;
            EXIT;
          end;

          for j := 1 to NodeSchema.childNodes.Length - 1 do
            Result.setAttribute(NodeSchema.childNodes.item[j].nodeName, Value.Strings[j]);

          Result := Result.ownerDocument;
        end;
      finally
        Free;
        Value.Free;
      end;
      except
        ExceptionLog( 'Сбой Un_Schems.LineTextFileToXml');
    end;

end;

{~GetVersionSchemaEx
  загрузка номера версии схемы.
  это значение из атрибута "value" или "default" в узле "//FK/NUM_VER".
  вызов этой подпрограммы добавлен в подпрограмму Un_Schems.LoadSchema,
  чтобы всегда иметь версию текущей загруженной схемы.}
function GetVersionSchemaEx( var aSchema: Variant): string;
begin

    try
      Result := '?';
      if VarIsNull( aSchema)
        then WriteLog( 'Схема не указана (=NULL).')
        else
          if NOT GetNodeAttribute( aSchema, '//FK/NUM_VER@value', Result)
            then
          if NOT GetNodeAttribute( aSchema, '//FK/NUM_VER@default', Result)
            then begin
              WriteLog( 'Не найдено атрибута описания версии (value/default) в разделе FK.NUM_VER.');
              Result := '?';
            end;

      except
        ExceptionLog( 'Сбой скрипта: "Un_Schems.LoadVersionSchemaEx".');
    end;

end;
{==============================================================================}
{~CreateXmlForExport
  создается документ XML для формирования документа для выгрузки.
  aSchema - схема с описанием формируемого документа.
            в схеме обязательно должен присутствовать раздел FK.
            ДОКУСКАЕТСЯ ПЕРЕДАВАТЬ ЗНАЧЕНИЕ NULL. при этом новый
            документ будет пустым (раздел FK не будет создан и заполнен)!
  aDocumentNew - в этом параметре возвращается корневой раздел созданного
            документа (<FORMAT>). это удобно для последующего заполнения
            созданного документа.
  ~result~
    созданных XML в виде:
      <FORMAT>
        <FK>версия</FK>}
function CreateXmlForExport( var aDocumentNew: Variant;
                             var aSchema: Variant): Variant;
var
  xDataFK: Variant;
begin

    try

      Result := CreateOleObject('Microsoft.XmlDom');
      Result.AppendChild( Result.createElement('FORMAT'));
      aDocumentNew := Result.documentElement;
      // проверяем указание схемы. если схема не указана, раздел FK не создаем.
      // но при этом результат, на всякий случай, уже создан но еще не заполнен.

      if NOT VarisNull( aSchema) then begin

        // создаем разделы в экспортируемых данных
        xDataFK   := aDocumentNew.AppendChild(
                                  FillElementBySchemaEx( aSchema, 'FK', NULL));
        xDataFK.setAttribute( 'FORM_VER', AppVersion);
        xDataFK.setAttribute( 'NUM_VER', GetVersionSchemaEx( aSchema));
        xDataFK.setAttribute( 'FORMER', AzkVersion);
      end;

      except
        ExceptionLog( 'Сбой Un_Schems.CreateXmlForExport');
    end;

end;

{~CreateXmlForExportFull
  создается документ XML для формирования документа для выгрузки с разделами
  FROM и TO. эти разделы заполняются указанными значениями.
  aSchema - схема с описанием формируемого документа.
            в схеме обязательно должен присутствовать раздел FK.
            ДОКУСКАЕТСЯ ПЕРЕДАВАТЬ ЗНАЧЕНИЕ NULL. при этом новый
            документ будет пустым (раздел FK не будет создан и заполнен)!
  aDocumentNew - в этом параметре возвращается корневой раздел созданного
            документа (<FORMAT>). это удобно для последующего заполнения
            созданного документа.
  aFromBudgLevel - FROM.BUDG_LEVEL
  aFromKodUbp - FROM.KOD_UBP
  aFromNameUbp - FROM.NAME_UBP
  aToKodTofk - TO.KOD_TOFK
  aToNameTofk - TO.NAME_TOFK
  ~result~
    созданных XML в виде:
      <FORMAT>
        <FK>версия</FK>
        <FROM>...</FROM>
        <TO>...</TO>}
function CreateXmlForExportFull( var aDocumentNew: Variant;
                                 var aSchema: Variant;
                                 aFromBudgLevel: string;
                                 aFromKodUbp: string;
                                 aFromNameUbp: string;
                                 aToKodTofk: string;
                                 aToNameTofk: string): Variant;
var
  xDataOutXmlSection: Variant;
begin

    try
      Result := CreateXmlForExport( aDocumentNew, aSchema);
      // создаем разделы FROM и TO в экспортируемых данных и
      // заполняем их данными из строки описания группы

      xDataOutXmlSection := aDocumentNew.AppendChild(
                                FillElementBySchemaEx( aSchema, 'FROM', NULL));
      // xFromBudgLevel
      xDataOutXmlSection.setAttribute( 'BUDG_LEVEL', aFromBudgLevel);
      // xFromKodUbp
      xDataOutXmlSection.setAttribute( 'KOD_UBP', aFromKodUbp);
      // xFromNameUbp
      xDataOutXmlSection.setAttribute( 'NAME_UBP', aFromNameUbp);

      xDataOutXmlSection := aDocumentNew.AppendChild(
                                FillElementBySchemaEx( aSchema, 'TO', NULL));
      // xToKodTofk
      xDataOutXmlSection.setAttribute( 'KOD_TOFK', aToKodTofk);
      // xToNameTofk
      xDataOutXmlSection.setAttribute( 'NAME_TOFK', aToNameTofk);
      except
        ExceptionLog( 'Сбой Un_Schems.CreateXmlForExportFull');
    end;

end;


{~GroupDocumentsXML
  группирует разделы XML документа aSource.
  в результате формируется древовидный документ с подразделами.
  группируем разделы: ZR, ZV, PPT, UF }
function GroupDocumentsXML( var aSource: Variant): Variant;

  procedure GroupNodes_( var aSourceDoc: Variant;
                         var aTo: Variant;
                         var aIndex: integer;
                         aNodexCount: integer;
                         aNodeGroup: string;
                         aNodeName: string);
  var
    xNode: Variant;
    xGroupNode: Variant;
  begin
    try
      // создаем подраздел
      xGroupNode := aTo.appendChild(
                        aTo.ownerDocument.createElement( aNodeGroup));
      // перебираем все ветви, от предыдущей (начало подраздела) до тех пор,
      // пока подразделы имеют название aNodeName или до конца XML
      DEC( aIndex);
      while (aIndex < aNodexCount) do begin
        xNode := aSourceDoc.childNodes.Item[ aIndex];
        INC( aIndex);
        // если текущий узел не соответствует aNodeName - завершаем
        // разбор группы. при этом индекс этого узла возвращаем как результат.
        // с этого узла начнется анализ следующей группы.
        if (xNode.nodeName <> aNodeName) then begin
          DEC( aIndex);
          EXIT;
        end;
        // добавяем текущий узел к группе
        xGroupNode.appendChild( xNode.cloneNode( TRUE));
      end;//while//
      except
        ExceptionLog( 'Сбой скрипта: "Imp_FK_BH.GroupDocumentsXML.GroupNodes_".');
    end;
  end;

  procedure Group_( var aSourceDoc: Variant;
                    var aTo: Variant;
                    var aIndex: integer;
                    aNodexCount: integer);
  var
    xSourceNode: Variant;
    xGroupNode: Variant;
    xGroupName: string;
    xNode: Variant;
  begin
    try
      // создаем подраздел группы
      xSourceNode:= aSourceDoc.childNodes.Item[ aIndex - 1];
      xGroupName := xSourceNode.nodeName;
      xGroupNode := aTo.appendChild( xSourceNode.cloneNode( TRUE));
      // формируем структуру группы (ZR/UF/PPT/...)
      while (aIndex < aNodexCount) do begin
        xNode := aSourceDoc.childNodes.Item[ aIndex];
        INC( aIndex);
        case xGroupName of
          'ZR':
              case xNode.nodeName of
                'ZROSN':
                    GroupNodes_(
                        aSourceDoc,
                        xGroupNode,
                        aIndex,
                        aNodexCount,
                        'ZROSN',
                        'ZROSN');
                'ZRST':
                    GroupNodes_(
                        aSourceDoc,
                        xGroupNode,
                        aIndex,
                        aNodexCount,
                        'ZRST',
                        'ZRST');
                else begin
                  DEC( aIndex);
                  BREAK;
                end;
              end;//case//

          'PPT':
              case xNode.nodeName of
                'PPST':
                    GroupNodes_(
                        aSourceDoc,
                        xGroupNode,
                        aIndex,
                        aNodexCount,
                        'PPST',
                        'PPST');
                else begin
                  DEC( aIndex);
                  BREAK;
                end;
              end;//case//

          'ZV':
              case xNode.nodeName of
                // разделы, входящие в ZV по одному разу
                'ZVDOCOSN',
                'ZVDOCVOZ',
                'ZVDOCPOL',
                'ZVDOC':
                    xGroupNode.appendChild( xNode.cloneNode( TRUE));
                else begin
                  DEC( aIndex);
                  BREAK;
                end;
              end;//case//
              
          'UF':
              case xNode.nodeName of
                'UFPP':
                    GroupNodes_(
                        aSourceDoc,
                        xGroupNode,
                        aIndex,
                        aNodexCount,
                        'UFPP',
                        'UFPP');
                'UFPP_N':
                    GroupNodes_(
                        aSourceDoc,
                        xGroupNode,
                        aIndex,
                        aNodexCount,
                        'UFPP_N',
                        'UFPP_N');
                else begin
                  DEC( aIndex);
                  BREAK;
                end;
              end;//case//

          else begin
            DEC( aIndex);
            BREAK;
          end;
        end;//case//
      end;//while//
      except
        ExceptionLog( 'Сбой скрипта: "Imp_FK_BH.GroupDocumentsXML.GroupNodes_".');
    end;
  end;

var
  xResultDoc: Variant;
  xSourceDoc: Variant;
  xSourceNode: Variant;
  xIndex: integer;
  xNodesCount: integer;
begin
  try
    
    WriteLog('Группировка разделов в документе...', True);

    // создаем сгруппированный документ
    Result     := CreateOleObject('Microsoft.XmlDom');
    xResultDoc := Result.appendChild( Result.createElement('ROOT'));
    // корневой элемент источника
    xSourceDoc := aSource.documentElement;
    // перебираем ветви разделов и добавляем к ним подразделы.
    xIndex      := 0;
    xNodesCount := xSourceDoc.childNodes.length;
    while (xIndex < xNodesCount) do begin
      xSourceNode := xSourceDoc.childNodes.Item[ xIndex];
      INC( xIndex);
      case xSourceNode.nodeName of
        // эти ветви игнорируем
        'FK', 'FROM', 'TO', 'BH': CONTINUE;
//            xResultDoc.appendChild( xSourceNode.cloneNode( TRUE));
        // эти группируем
        'ZR', 'PPT', 'ZV', 'UF':
            Group_( xSourceDoc, xResultDoc, xIndex, xNodesCount);
        // остальные неопределенные
        else raise( FORMAT( 'Неопределенный раздел: "%s".',
                            [ xSourceNode.nodeName]));
      end;//case//
    end;//while//

    except
      ExceptionLog( 'Сбой скрипта: "Un_Schems.GroupDocumentsXML".');
  end;
end;


{---------------------------------------------------------------------------}
{~LoadFileFK_and_ValidateHTML
  загрузка файла FK (aFileName) в виде XML
  и валидация его по схеме aFileNameSchema в формате HTML.
  сдесь же проверяется формат файла ФК по схеме, поэтому схема (глобальная
  переменная Schema) обязательно должна быть загружена подпрограммой LoadSchema
  до вызова этой подпрограммы.
  результат:
    NULL - если есть проблема загрузки файла ФК, или валидация с ошибками
            и дальнейшая обработка файла отклонена.
    Xml (documentElement) - файл ФК.
          фактически дальше можно обрабаты список узлов корневого элемента.}
function LoadFileFK_and_ValidateHTML( aFileName, aFileNameSchema: string): variant;
var
  xSchema_FormVer: string;
  xData_FormVer: string;
begin

    try
      Result := NULL;

      if NOT FileExists( aFileName) then begin
        WriteInfo( FORMAT( 'Файл %s не найден или нет доступа.',
                            [ aFileName]));
        EXIT;
      end;

      // валидация HTML выполняется теперь в эксче намного быстрее
      // чем скриптовым языком
      if NOT ValidateFileFK_Load( aFileName, aFileNameSchema)
        then EXIT;

      WriteLog( FORMAT( 'Загружается файл %s...', [ aFileName]), TRUE);
      Data := LoadFKFileToXmlRTTI( aFileName, aFileNameSchema);

      if VarIsNULL( Data) then begin
        WriteInfo( FORMAT( 'Файл %s не загружен.',
                            [ aFileName]));
        EXIT;
      end;
      if (Data.documentElement.childNodes.Length <= 0) then begin
        WriteInfo( FORMAT( 'Файл %s не содержит данных.',
                            [ aFileName]));
        EXIT;
      end;

      WriteLog('Проверка версии формата файла', True);
      xSchema_FormVer :=
          Schema.documentElement.selectSingleNode('FK').
                            selectSingleNode('NUM_VER').getAttribute('value');
      xData_FormVer :=
          GetNodeAttributeEx( Data.documentElement.childNodes.Item[0], 'NUM_VER');
      if (xSchema_FormVer <> xData_FormVer) then begin
        WriteInfo(
            FORMAT( 'Версия формата %s в файле %s не поддерживается',
                    [ xData_FormVer,
                      aFileName]));
        Exit;
      end;

      Result := Data.documentElement;
      WriteLog( 'Файл загружен успешно.');
      except
        ExceptionProc( 'LoadFileFK_and_ValidateHTML');
    end;

end;


begin
end.
