Денис Степулёнок ([info]stden) wrote,
@ 2007-04-18 17:11:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:кодогенерация, программирование

Собственный XML Data Binding для Delphi

Встроенный мастер Delphi
Я думаю, каждый, кто использовал XML в Delphi, наверняка использовал мастер XML Data Binding Wizard (хотя, быть может, кто-то писал классы-обёртки для xml вручную, или вообще работал напрямую с компонентом TXMLDocument, но мне лениво :)). Этот мастер позволяет по xml файлу с каким-либо данными получить модуль на языке Pascal для удобной работы с xml файлом:

Он (в версии для TurboDelphi) для каждого варианта xml-тэга (варианты отличаются наличием/отсутствием отдельных атрибутов) генерирует свой вариант объекта:
ButtonType2, ButtonType22, ButtonType222, ButtonType2222, что, мягко говоря, неудобно.
К тому же мастер нельзя запускать из командной строки (хотелось бы запускать его автоматически при сборке приложения).
Более корректная генерация с помощью сторонних средств
Более корректно мастер в Delphi работает, если сначала открыть XML файл в Visual Studio (испытывал на Visual Studio 2005), затем получить его схему, сохранить схему и использовать в мастере в Delphi.
В комплект Visual Studio входит утилитка xsd.exe (у меня находится в каталоге C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\), она позволяет создать схему из XML файла, и сгенерировать классы на C#, VB, J#, J#Script, C++ (Pascal в этом списке нет!).
xsd.exe .xml [/outputdir:] <-- Генерация xsd файла по xml файлу.
xsd.exe .xsd /classes|dataset [/e:] [/l:] [/n:] [/o:] [/s] [/uri:] <-- Генерация классов по xsd файлу.
Выбор средств реализации
На каком языке писать свой мастер:
- на Delphi - "+" - пишем генератор на том же языке, что и генерируемый код, возможно, им будут пользоваться люди, которые знают только Delphi - они поймут исходный код и им не надо будет ставить дополнительный компилятор чтобы "собрать" программу;
- на C# под .NET 2.0 - "+" - можно использовать развитые компоненты .NET 2.0 для работы с XML, "-" - требуется .NET Framework 2.0 для компиляции программы. "+++" - в Framework есть ПРОСТО ЗАМЕЧАТЕЛЬНЫЙ КЛАСС XmlSchemaInference!! он позволяет извлекать схему из XML-файла.
Взвесив "за" и "против" я выбрал C# 2.0.
Реализация
Программа работает в 2 этапа:
1. Получение "правильной" XSD схемы по XML файлу.
2. Генерация модуля на Pascal для работы с этим XML файлом.
Кодогенерация выполняется "в лоб". Неплохо было бы переделать по принципам статьи http://www.rsdn.ru/article/dotnet/codegen.xml - Алгоритмы кодогенерации :)
Исходный код большой и некрасивый :( Протестировано под TurboDelphi. Скомпилировано под Visual Studio C# 2.0. Наверное, использовать свойства (property - set) при кодогенерации вообще нехорошо, хотя очень удобно просматривать в отладчике генерируемые куски кода.
Исходный текст программы

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Schema;

namespace XmlSchema
{
  public class Utils{ public const string NewLine = "\r\n"; }
  /// 
  /// Базовый класс для XML Тэга, реализует всё что нужно для XML Атрибута
  /// 
  public class Атрибут
  {
    public bool Повторяется = false; // Может ли многократно повторяться этот тэг в XML файле
    public string имяXML; // Имя тэга/атрибута в XML файле
    // Идентификатор для языка программирования
    public string Id { get { return MyDelphiXMLDataBinding.toTranslit(имяXML.Substring(0, 1).ToUpper() + имяXML.Substring(1)); } }
    // Код типа в XML (исходный код для опеределения типа)
    public XmlTypeCode TypeCode;
    public string InterfaceName{ get { return "IXML" + Id; } }
    public string TypeName{ get { return "TXML" + Id; } }

    public string PascalType
    {
      get
      {
        switch (TypeCode)
        {
          case XmlTypeCode.UnsignedInt:
          case XmlTypeCode.UnsignedShort:
          case XmlTypeCode.Short:
          case XmlTypeCode.UnsignedByte:
          case XmlTypeCode.Byte:
            return "Integer";
          case XmlTypeCode.String:
            return "WideString";
          case XmlTypeCode.None:
            if (Повторяется)
              return InterfaceName + "List";
            else
              return InterfaceName;
          case XmlTypeCode.Boolean:
            return "Boolean";
          default:
            // Метод "постепенного наращивания", если будет использован неизвестный тип, нужно добавить
            // в этот метод его обработку
            string text = TypeCode.ToString();
            Clipboard.SetText(text);
            throw new Exception(text);
        }
      }
    }

    public string Get_Header{ get { return "function Get_" + Id + ": " + PascalType + ";"; } }
    public string Set_Header{ 
      get
      {
        if (TypeCode != XmlTypeCode.None)
          return "procedure Set_" + Id + "(Value: " + PascalType + ");";
        else
          return "";
      }
    }

    public string Property_Decl
    {
      get
      {
        if (TypeCode != XmlTypeCode.None)
          return "property " + Id + ": " + PascalType + " read Get_" + Id + " write Set_" + Id + ";";
        else
          return "property " + Id + ": " + PascalType + " read Get_" + Id + ";";
      }
    }
  }

  public class Тэг : Атрибут
  {
    public static List<Тэг> Список = new List<Тэг>();
    private Guid GUID;
    private Guid ListGUID;
    public Тэг()
    {
      GUID = Guid.NewGuid();
      ListGUID = Guid.NewGuid();
      Список.Add(this);
    }
    public List<Атрибут> Атрибуты = new List<Атрибут>();
    public List<Тэг> ВложенныеТэги = new List<Тэг>();
    public bool ПростойТип = false;
    public string Заголовок_Interface{ get { return InterfaceName + " = interface;"; } }
    public string Заголовок_Type{ get { return TypeName + " = class;"; } }

    private int Табуляция = 0;

    public void Add(ref string исходнаяСтрока, string чтоДобавить)
    {
      if (чтоДобавить != "")
      {
        string tab = "";
        for (int i = 0; i < Табуляция; i++) tab += " ";
        исходнаяСтрока = исходнаяСтрока + Utils.NewLine + tab + чтоДобавить;
      }
    }

    public string Описание_Interface
    {
      get
      {
        string res = @"{ " + InterfaceName + @" }

  " + InterfaceName + " = interface(IXMLNode)";
        Табуляция = 4;
        Add(ref res, "['{" + GUID.ToString().ToUpper() + "}']");
        Add(ref res, "{ Property Accessors }");
        res = Заголовки_GetSet(res);
        Add(ref res, "{ Methods & Properties }");
        foreach (Атрибут a in Атрибуты) Add(ref res, a.Property_Decl);
        foreach (Тэг a in ВложенныеТэги) Add(ref res, a.Property_Decl);
        res += Utils.NewLine + "  end;" + Utils.NewLine;
        return res;
      }
    }

    private string Заголовки_GetSet(string res)
    {
      foreach (Атрибут a in Атрибуты) Add(ref res, a.Get_Header);
      foreach (Тэг o in ВложенныеТэги) Add(ref res, o.Get_Header);
      foreach (Атрибут a in Атрибуты) Add(ref res, a.Set_Header);
      foreach (Тэг o in ВложенныеТэги) Add(ref res, o.Set_Header);
      return res;
    }

    public string TXMLTypeDec
    {
      get
      {
        string res = @"  " + TypeName + " = class(TXMLNode, " + InterfaceName + @")";
        if (НаборПолей != "")
        {
          res += Utils.NewLine + "  private";
          res += НаборПолей;
        }
        res += Utils.NewLine + "  protected";
        res += Utils.NewLine + "    { " + InterfaceName + @" }";
        res = Заголовки_GetSet(res);
        if (ТелоAfterConstruction != "")
        {
          res += @"  
  public
    procedure AfterConstruction; override;";
        }
        return res + @"
  end;
";
      }
    }

    private string НаборПолей
    {
      get
      {
        string res = "";
        foreach (Тэг t in ВложенныеТэги)
          if (t.Повторяется)
            res += Utils.NewLine + "    F" + t.Id + ": " + t.InterfaceName + "List;";
        return res;
      }
    }

    public string GlobalFunctionsDecl
    {
      get
      {
        return
          @"{ Global Functions }

function Get" + Id + @"(Doc: IXMLDocument): " + InterfaceName +
          @";
function Load" +
          Id + @"(const FileName: WideString): " + InterfaceName + @";
function New" + Id +
          @": " + InterfaceName + @";";
      }
    }

    public string GlobalFunctionsImplementation
    {
      get
      {
        return
          @"{ Global Functions }

function Get" + Id + @"(Doc: IXMLDocument): " + InterfaceName +
          @";
begin
  Result := Doc.GetDocBinding('" + имяXML + @"', " + TypeName +
          @", TargetNamespace) as " + InterfaceName + @";
end;

function Load" + Id +
          @"(const FileName: WideString): " + InterfaceName +
          @";
begin
  Result := LoadXMLDocument(FileName).GetDocBinding('" + имяXML + @"', " + TypeName +
          @", TargetNamespace) as " + InterfaceName + @";
end;

function New" + Id + @": " + InterfaceName +
          @";
begin
  Result := NewXMLDocument.GetDocBinding('" + имяXML + @"', " + TypeName +
          @", TargetNamespace) as " + InterfaceName + @";
end;";
      }
    }

    public string Заголовок_InterfaceList{ get { return InterfaceName + @"List = interface;"; } }

    public string Описание_InterfaceList
    {
      get
      {
        return
          @"{ " + InterfaceName + @"List }

  " + InterfaceName + @"List = interface(IXMLNodeCollection)
    ['{" +
          ListGUID.ToString().ToUpper() + @"}']
    { Methods & Properties }
    function Add: " + InterfaceName +
          @";
    function Insert(const Index: Integer): " + InterfaceName +
          @";
    function Get_Item(Index: Integer): " + InterfaceName + @";
    property Items[Index: Integer]: " +
          InterfaceName + @" read Get_Item; default;
  end;
";
      }
    }

    public string Заголовок_TypeList{ get { return "" + TypeName + "List = class;"; } }

    public string ТелоAfterConstruction
    {
      get
      {
        string res = "";
        foreach (Тэг t in ВложенныеТэги)
          if (!t.ПростойТип)
            res += Utils.NewLine + "  RegisterChildNode('" + t.имяXML + "', " + t.TypeName + ");";
        foreach (Тэг t in ВложенныеТэги)
        {
          if (t.Повторяется)
            res += Utils.NewLine + "  F" + t.Id + " := CreateCollection(" + t.TypeName +
                   "List, " + t.InterfaceName + ", '" + t.имяXML + "') as " + t.InterfaceName + "List;";
        }
        return res;
      }
    }
  }

  public class MyDelphiXMLDataBinding
  {
    private static string имяМодуля;

    public static string Header
    {
      get
      {
        return
          @"// Модуль сгенерирован " + DateTime.Now.ToString() +
          @" программой XmlSchema
// По всем вопросам обращайтесь к Денису Степулёнку +79117117850 (super.denis@gmail.com)

unit " +
          имяМодуля + @";

interface

uses xmldom, XMLDoc, XMLIntf;

type

";
      }
    }

    [STAThread]
    private static void Main(string[] args)
    {
      if (args.Length >= 2)
      {
        string ИмяИсходногоXMLФайла = args[0];
        string ИмяВыходногоPascalФайла = args[1];
        XmlSchemaSet schemaSet = ПолучениеСхемыXMLФайла(ИмяИсходногоXMLФайла);
        System.Xml.Schema.XmlSchema перваяСхема = ПерваяСхема(schemaSet);
        if (args.Length == 3)
        {
          string ИмяФайлаДляСохраненияXSDСхемы = args[2];
          ЗаписатьXSDСхемуВФайл(перваяСхема, ИмяФайлаДляСохраненияXSDСхемы);
        }
        // Создаём в памяти дерево по XSD схеме
        Тэг корень = null;
        foreach (DictionaryEntry de in перваяСхема.Elements)
        {
          корень = Build((XmlSchemaElement) de.Value);
        }
        ГенерацияВыходногоPascalФайла(корень, ИмяВыходногоPascalФайла);
      }
      else
      {
        Console.WriteLine("XmlSchema.exe <ИмяИсходногоXMLФайла> <ИмяВыходногоPascalФайла> [<ИмяФайлаДляСохраненияXSDСхемы>]");
      }
    }

    private static void ГенерацияВыходногоPascalФайла(Тэг корень, string имяФайла)
    {
      // Получаем имя модуля по названию pascal файла
      имяМодуля = new FileInfo(имяФайла).Name.ToLower().Replace(".pas", "");
      // Делаем первую букву названия модуля заглавной
      имяМодуля = имяМодуля.Substring(0, 1).ToUpper() + имяМодуля.Substring(1);
      using (StreamWriter wr = new StreamWriter(имяФайла, false, Encoding.GetEncoding("Windows-1251")))
      {
        wr.Write(Header);
        wr.WriteLine("{ Декларация интерфейсов }");
        foreach (Тэг t in Тэг.Список)
        {
          if (t.ПростойТип) continue;
          wr.WriteLine("  " + t.Заголовок_Interface);
          if (t.Повторяется) wr.WriteLine("  " + t.Заголовок_InterfaceList);
        }
        wr.WriteLine();
        foreach (Тэг t in Тэг.Список)
        {
          if (t.ПростойТип) continue;
          wr.WriteLine(t.Описание_Interface);
          if (t.Повторяется)
            wr.WriteLine(t.Описание_InterfaceList);
        }
        ДекларацияТипов(wr);

        foreach (Тэг t in Тэг.Список)
        {
          if (t.ПростойТип) continue;
          wr.WriteLine("{ " + t.TypeName + " }");
          wr.WriteLine();
          wr.WriteLine(t.TXMLTypeDec);

          if (t.Повторяется)
          {
            wr.WriteLine(
              @"
{ " + t.TypeName + @"List }

  " + t.TypeName + @"List = class(TXMLNodeCollection, " +
              t.InterfaceName + @"List)
  protected
    { " + t.InterfaceName + @"List }
    function Add: " +
              t.InterfaceName + @";
    function Insert(const Index: Integer): " + t.InterfaceName +
              @";
    function Get_Item(Index: Integer): " + t.InterfaceName + @";
  end;
");
          }
        }

        wr.WriteLine(корень.GlobalFunctionsDecl);
        wr.WriteLine();
        wr.WriteLine(@"const
  TargetNamespace = '';");
        wr.WriteLine();
        wr.WriteLine("implementation");
        wr.WriteLine();
        wr.WriteLine(корень.GlobalFunctionsImplementation);
        foreach (Тэг t in Тэг.Список)
        {
          if (t.ПростойТип) continue;

          List<Тэг> СписокList = new List<Тэг>();
          foreach (Тэг child in t.ВложенныеТэги)
            if (child.Повторяется)
              СписокList.Add(child);

          wr.WriteLine();
          wr.WriteLine("{ " + t.TypeName + " }");
          wr.WriteLine();
          string телоAC = t.ТелоAfterConstruction;
          if (телоAC != "")
          {
            wr.WriteLine("procedure " + t.TypeName + ".AfterConstruction;");
            wr.WriteLine("begin");
            wr.WriteLine(телоAC);
            wr.WriteLine("  inherited;");
            wr.WriteLine("end;");
          }
          foreach (Тэг child in t.ВложенныеТэги)
          {
            if (child.ПростойТип)
            {
              wr.WriteLine();
              wr.WriteLine("function " + t.TypeName + ".Get_" + child.Id + ": " + child.PascalType + ";");
              wr.WriteLine("begin");
              if (child.TypeCode == XmlTypeCode.String)
              {
                wr.WriteLine("  Result := ChildNodes['" + child.имяXML + "'].Text;");
              }
              else
              {
                wr.WriteLine("  Result := ChildNodes['" + child.имяXML + "'].NodeValue;");
              }
              wr.WriteLine("end;");
              wr.WriteLine();
              wr.WriteLine("procedure " + t.TypeName + ".Set_" + child.Id + "(Value: " + child.PascalType +
                           ");");
              wr.WriteLine("begin");
              wr.WriteLine("  ChildNodes['" + child.имяXML + "'].NodeValue := Value;");
              wr.WriteLine("end;");
            }
            else
            {
              if (child.Повторяется)
              {
                wr.WriteLine(@"
function " + t.TypeName + @".Get_" + child.Id + ": " + child.InterfaceName +
                             @"List;
begin
  Result := F" + child.Id + @";
end;");
              }
              else
              {
                wr.WriteLine(@"
function " + t.TypeName + @".Get_" + child.Id + @": " + child.InterfaceName +
                             @";
begin
  Result := ChildNodes['" + child.имяXML + "'] as " + child.InterfaceName +
                             @";
end;");
              }
            }
          }
          foreach (Атрибут a in t.Атрибуты)
          {
            wr.WriteLine(@"
function " + t.TypeName + @".Get_" + a.Id + @": " + a.PascalType +
                         @";
begin");
            if (a.TypeCode == XmlTypeCode.String)
            {
              wr.WriteLine("  Result := AttributeNodes['" + a.имяXML + "'].Text;");
            }
            else
            {
              wr.WriteLine("  Result := AttributeNodes['" + a.имяXML + "'].NodeValue;");
            }
            wr.WriteLine(
              @"end;

procedure " + t.TypeName + @".Set_" + a.Id + @"(Value: " + a.PascalType +
              @");
begin
  SetAttribute('" + a.имяXML + @"', Value);
end;");
          }
          if (t.Повторяется)
          {
            wr.WriteLine(МетодыРаботыСоСписком(t));
          }
        }
        wr.WriteLine("end.");
      }
    }

    private static void ДекларацияТипов(StreamWriter wr)
    {
      wr.WriteLine();
      wr.WriteLine("{ Декларация типов }");
      wr.WriteLine();
      foreach (Тэг t in Тэг.Список)
      {
        if (t.ПростойТип) continue;
        wr.WriteLine("  " + t.Заголовок_Type);
        if (t.Повторяется)
          wr.WriteLine("  " + t.Заголовок_TypeList);
      }
      wr.WriteLine();
    }

    private static string МетодыРаботыСоСписком(Тэг тэг)
    {
      return
        @"
{ TXML%id%List }

function TXML%id%List.Add: IXML%id%;
begin
  Result := AddItem(-1) as IXML%id%;
end;

function TXML%id%List.Insert(const Index: Integer): IXML%id%;
begin
  Result := AddItem(Index) as IXML%id%;
end;

function TXML%id%List.Get_Item(Index: Integer): IXML%id%;
begin
  Result := List[Index] as IXML%id%;
end;
"
          .Replace("%id%", тэг.Id);
    }

    private static System.Xml.Schema.XmlSchema ПерваяСхема(XmlSchemaSet schemaSet)
    {
      foreach (System.Xml.Schema.XmlSchema schema in schemaSet.Schemas())
      {
        return schema;
      }
      throw new Exception("Из XML файла не было получено ни одной схемы!");
    }

    public static XmlSchemaSet ПолучениеСхемыXMLФайла(string ИмяИсходногоXMLФайла)
    {
      XmlSchemaSet schemaSet;
      using (XmlTextReader reader = new XmlTextReader(ИмяИсходногоXMLФайла))
      {
        XmlSchemaInference schemaInference = new XmlSchemaInference();
        schemaSet = schemaInference.InferSchema(reader);
      }
      return schemaSet;
    }

    private static void ЗаписатьXSDСхемуВФайл(System.Xml.Schema.XmlSchema schema, string ИмяФайлаДляСохраненияXSDСхемы)
    {
      using (XmlTextWriter writer = new XmlTextWriter(ИмяФайлаДляСохраненияXSDСхемы,
                                                      Encoding.GetEncoding("Windows-1251")))
      {
        writer.Indentation = 2;
        writer.IndentChar = ' ';
        writer.Formatting = Formatting.Indented;
        schema.Write(writer);
      }
    }

    public static Тэг Build(XmlSchemaObject schemaObject)
    {
      if (schemaObject == null) return null;
      Тэг тэг;
      Type type = schemaObject.GetType();
      switch (type.Name)
      {
        case "XmlSchemaElement":
          XmlSchemaElement element = (XmlSchemaElement) schemaObject;
          тэг = Build(element.ElementSchemaType);
          if (тэг == null)
          {
            тэг = new Тэг();
            тэг.ПростойТип = true;
          }
          тэг.имяXML = element.Name;
          тэг.TypeCode = element.ElementSchemaType.TypeCode;
          тэг.Повторяется = (element.MaxOccursString == "unbounded");
          break;
        case "XmlSchemaComplexType":
          XmlSchemaComplexType complexType = (XmlSchemaComplexType) schemaObject;
          тэг = Build(complexType.Particle);
          if (тэг == null)
          {
            тэг = new Тэг();
          }
          foreach (XmlSchemaAttribute schemaAttribute in complexType.Attributes)
          {
            Атрибут атрибут = new Атрибут();
            атрибут.имяXML = schemaAttribute.Name;
            атрибут.TypeCode = schemaAttribute.AttributeSchemaType.TypeCode;
            тэг.Атрибуты.Add(атрибут);
          }
          break;
        case "XmlSchemaSequence":
          XmlSchemaSequence schemaSequence = (XmlSchemaSequence) schemaObject;
          тэг = new Тэг();
          foreach (XmlSchemaObject item in schemaSequence.Items)
          {
            тэг.ВложенныеТэги.Add(Build(item));
          }
          break;
        case "XmlSchemaSimpleType":
          return null;
        default:
          string text = "case \"" + type.Name + "\":\n " + type.Name + " x = (" + type.Name + ") schemaObject;\n" +
                        "  break;";
          Clipboard.SetText(text);
          throw new Exception(text);
      }
      if (тэг == null) throw new Exception("Не сработал switch! " + schemaObject);
      return тэг;
    }

    public static string toTranslit(string str)
    {
      Dictionary
[Error: Irreparable invalid markup ('<char,>') in entry. Owner must fix manually. Raw contents below.]

<lj-cut text="XML Data Binding (описание идеи и исходный текст)">
<FONT size=+1>Встроенный мастер Delphi</FONT>
Я думаю, каждый, кто использовал XML в Delphi, наверняка использовал мастер <B>XML Data Binding Wizard</B> (хотя, быть может, кто-то писал классы-обёртки для xml вручную, или вообще работал напрямую с компонентом TXMLDocument, но мне лениво :)). Этот мастер позволяет по xml файлу с каким-либо данными получить модуль на языке Pascal для удобной работы с xml файлом:
<A href="http://img143.imageshack.us/my.php?image=file3ti6.png"><IMG src="http://img143.imageshack.us/img143/2456/file3ti6.th.png"></A> <A href="http://img83.imageshack.us/my.php?image=file4sk4.png"><IMG src="http://img83.imageshack.us/img83/6556/file4sk4.th.png"></A>
Он (в версии для TurboDelphi) для каждого варианта xml-тэга (варианты отличаются наличием/отсутствием отдельных атрибутов) генерирует свой вариант объекта:
ButtonType2, ButtonType22, ButtonType222, ButtonType2222, что, мягко говоря, неудобно.
К тому же мастер нельзя запускать из командной строки (хотелось бы запускать его автоматически при сборке приложения).
<FONT size=+1>Более корректная генерация с помощью сторонних средств</FONT>
Более корректно мастер в Delphi работает, если сначала открыть XML файл в Visual Studio (испытывал на Visual Studio 2005), затем получить его схему, сохранить схему и использовать в мастере в Delphi.
В комплект Visual Studio входит утилитка <B>xsd.exe</B> (у меня находится в каталоге C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\), она позволяет создать схему из XML файла, и сгенерировать классы на C#, VB, J#, J#Script, C++ (Pascal в этом списке нет!).
xsd.exe <INSTANCE>.xml [/outputdir:] &lt;-- Генерация <B>xsd</B> файла по <B>xml</B> файлу.
xsd.exe <SCHEMA>.xsd /classes|dataset [/e:] [/l:] [/n:] [/o:] [/s] [/uri:] &lt;-- Генерация классов по <B>xsd</B> файлу.
<FONT size=+1>Выбор средств реализации</FONT>
На каком языке писать свой мастер:
- на Delphi - "+" - пишем генератор на том же языке, что и генерируемый код, возможно, им будут пользоваться люди, которые знают только Delphi - они поймут исходный код и им не надо будет ставить дополнительный компилятор чтобы "собрать" программу;
- на C# под .NET 2.0 - "+" - можно использовать развитые компоненты .NET 2.0 для работы с XML, "-" - требуется .NET Framework 2.0 для компиляции программы. "+++" - в Framework есть ПРОСТО ЗАМЕЧАТЕЛЬНЫЙ КЛАСС XmlSchemaInference!! он позволяет извлекать схему из XML-файла.
Взвесив "за" и "против" я выбрал C# 2.0.
<FONT size=+1>Реализация</FONT>
Программа работает в 2 этапа:
1. Получение "правильной" XSD схемы по XML файлу.
2. Генерация модуля на Pascal для работы с этим XML файлом.
Кодогенерация выполняется "в лоб". Неплохо было бы переделать по принципам статьи http://www.rsdn.ru/article/dotnet/codegen.xml - Алгоритмы кодогенерации :)
Исходный код большой и некрасивый :( Протестировано под TurboDelphi. Скомпилировано под Visual Studio C# 2.0. Наверное, использовать свойства (property - set) при кодогенерации вообще нехорошо, хотя очень удобно просматривать в отладчике генерируемые куски кода.
<FONT size=+1>Исходный текст программы</FONT>
<pre>
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Schema;

namespace XmlSchema
{
public class Utils{ public const string NewLine = "\r\n"; }
/// <summary>
/// Базовый класс для XML Тэга, реализует всё что нужно для XML Атрибута
/// </summary>
public class Атрибут
{
public bool Повторяется = false; // Может ли многократно повторяться этот тэг в XML файле
public string имяXML; // Имя тэга/атрибута в XML файле
// Идентификатор для языка программирования
public string Id { get { return MyDelphiXMLDataBinding.toTranslit(имяXML.Substring(0, 1).ToUpper() + имяXML.Substring(1)); } }
// Код типа в XML (исходный код для опеределения типа)
public XmlTypeCode TypeCode;
public string InterfaceName{ get { return "IXML" + Id; } }
public string TypeName{ get { return "TXML" + Id; } }

public string PascalType
{
get
{
switch (TypeCode)
{
case XmlTypeCode.UnsignedInt:
case XmlTypeCode.UnsignedShort:
case XmlTypeCode.Short:
case XmlTypeCode.UnsignedByte:
case XmlTypeCode.Byte:
return "Integer";
case XmlTypeCode.String:
return "WideString";
case XmlTypeCode.None:
if (Повторяется)
return InterfaceName + "List";
else
return InterfaceName;
case XmlTypeCode.Boolean:
return "Boolean";
default:
// Метод "постепенного наращивания", если будет использован неизвестный тип, нужно добавить
// в этот метод его обработку
string text = TypeCode.ToString();
Clipboard.SetText(text);
throw new Exception(text);
}
}
}

public string Get_Header{ get { return "function Get_" + Id + ": " + PascalType + ";"; } }
public string Set_Header{
get
{
if (TypeCode != XmlTypeCode.None)
return "procedure Set_" + Id + "(Value: " + PascalType + ");";
else
return "";
}
}

public string Property_Decl
{
get
{
if (TypeCode != XmlTypeCode.None)
return "property " + Id + ": " + PascalType + " read Get_" + Id + " write Set_" + Id + ";";
else
return "property " + Id + ": " + PascalType + " read Get_" + Id + ";";
}
}
}

public class Тэг : Атрибут
{
public static List<Тэг> Список = new List<Тэг>();
private Guid GUID;
private Guid ListGUID;
public Тэг()
{
GUID = Guid.NewGuid();
ListGUID = Guid.NewGuid();
Список.Add(this);
}
public List<Атрибут> Атрибуты = new List<Атрибут>();
public List<Тэг> ВложенныеТэги = new List<Тэг>();
public bool ПростойТип = false;
public string Заголовок_Interface{ get { return InterfaceName + " = interface;"; } }
public string Заголовок_Type{ get { return TypeName + " = class;"; } }

private int Табуляция = 0;

public void Add(ref string исходнаяСтрока, string чтоДобавить)
{
if (чтоДобавить != "")
{
string tab = "";
for (int i = 0; i < Табуляция; i++) tab += " ";
исходнаяСтрока = исходнаяСтрока + Utils.NewLine + tab + чтоДобавить;
}
}

public string Описание_Interface
{
get
{
string res = @"{ " + InterfaceName + @" }

" + InterfaceName + " = interface(IXMLNode)";
Табуляция = 4;
Add(ref res, "['{" + GUID.ToString().ToUpper() + "}']");
Add(ref res, "{ Property Accessors }");
res = Заголовки_GetSet(res);
Add(ref res, "{ Methods & Properties }");
foreach (Атрибут a in Атрибуты) Add(ref res, a.Property_Decl);
foreach (Тэг a in ВложенныеТэги) Add(ref res, a.Property_Decl);
res += Utils.NewLine + " end;" + Utils.NewLine;
return res;
}
}

private string Заголовки_GetSet(string res)
{
foreach (Атрибут a in Атрибуты) Add(ref res, a.Get_Header);
foreach (Тэг o in ВложенныеТэги) Add(ref res, o.Get_Header);
foreach (Атрибут a in Атрибуты) Add(ref res, a.Set_Header);
foreach (Тэг o in ВложенныеТэги) Add(ref res, o.Set_Header);
return res;
}

public string TXMLTypeDec
{
get
{
string res = @" " + TypeName + " = class(TXMLNode, " + InterfaceName + @")";
if (НаборПолей != "")
{
res += Utils.NewLine + " private";
res += НаборПолей;
}
res += Utils.NewLine + " protected";
res += Utils.NewLine + " { " + InterfaceName + @" }";
res = Заголовки_GetSet(res);
if (ТелоAfterConstruction != "")
{
res += @"
public
procedure AfterConstruction; override;";
}
return res + @"
end;
";
}
}

private string НаборПолей
{
get
{
string res = "";
foreach (Тэг t in ВложенныеТэги)
if (t.Повторяется)
res += Utils.NewLine + " F" + t.Id + ": " + t.InterfaceName + "List;";
return res;
}
}

public string GlobalFunctionsDecl
{
get
{
return
@"{ Global Functions }

function Get" + Id + @"(Doc: IXMLDocument): " + InterfaceName +
@";
function Load" +
Id + @"(const FileName: WideString): " + InterfaceName + @";
function New" + Id +
@": " + InterfaceName + @";";
}
}

public string GlobalFunctionsImplementation
{
get
{
return
@"{ Global Functions }

function Get" + Id + @"(Doc: IXMLDocument): " + InterfaceName +
@";
begin
Result := Doc.GetDocBinding('" + имяXML + @"', " + TypeName +
@", TargetNamespace) as " + InterfaceName + @";
end;

function Load" + Id +
@"(const FileName: WideString): " + InterfaceName +
@";
begin
Result := LoadXMLDocument(FileName).GetDocBinding('" + имяXML + @"', " + TypeName +
@", TargetNamespace) as " + InterfaceName + @";
end;

function New" + Id + @": " + InterfaceName +
@";
begin
Result := NewXMLDocument.GetDocBinding('" + имяXML + @"', " + TypeName +
@", TargetNamespace) as " + InterfaceName + @";
end;";
}
}

public string Заголовок_InterfaceList{ get { return InterfaceName + @"List = interface;"; } }

public string Описание_InterfaceList
{
get
{
return
@"{ " + InterfaceName + @"List }

" + InterfaceName + @"List = interface(IXMLNodeCollection)
['{" +
ListGUID.ToString().ToUpper() + @"}']
{ Methods & Properties }
function Add: " + InterfaceName +
@";
function Insert(const Index: Integer): " + InterfaceName +
@";
function Get_Item(Index: Integer): " + InterfaceName + @";
property Items[Index: Integer]: " +
InterfaceName + @" read Get_Item; default;
end;
";
}
}

public string Заголовок_TypeList{ get { return "" + TypeName + "List = class;"; } }

public string ТелоAfterConstruction
{
get
{
string res = "";
foreach (Тэг t in ВложенныеТэги)
if (!t.ПростойТип)
res += Utils.NewLine + " RegisterChildNode('" + t.имяXML + "', " + t.TypeName + ");";
foreach (Тэг t in ВложенныеТэги)
{
if (t.Повторяется)
res += Utils.NewLine + " F" + t.Id + " := CreateCollection(" + t.TypeName +
"List, " + t.InterfaceName + ", '" + t.имяXML + "') as " + t.InterfaceName + "List;";
}
return res;
}
}
}

public class MyDelphiXMLDataBinding
{
private static string имяМодуля;

public static string Header
{
get
{
return
@"// Модуль сгенерирован " + DateTime.Now.ToString() +
@" программой XmlSchema
// По всем вопросам обращайтесь к Денису Степулёнку +79117117850 (super.denis@gmail.com)

unit " +
имяМодуля + @";

interface

uses xmldom, XMLDoc, XMLIntf;

type

";
}
}

[STAThread]
private static void Main(string[] args)
{
if (args.Length >= 2)
{
string ИмяИсходногоXMLФайла = args[0];
string ИмяВыходногоPascalФайла = args[1];
XmlSchemaSet schemaSet = ПолучениеСхемыXMLФайла(ИмяИсходногоXMLФайла);
System.Xml.Schema.XmlSchema перваяСхема = ПерваяСхема(schemaSet);
if (args.Length == 3)
{
string ИмяФайлаДляСохраненияXSDСхемы = args[2];
ЗаписатьXSDСхемуВФайл(перваяСхема, ИмяФайлаДляСохраненияXSDСхемы);
}
// Создаём в памяти дерево по XSD схеме
Тэг корень = null;
foreach (DictionaryEntry de in перваяСхема.Elements)
{
корень = Build((XmlSchemaElement) de.Value);
}
ГенерацияВыходногоPascalФайла(корень, ИмяВыходногоPascalФайла);
}
else
{
Console.WriteLine("XmlSchema.exe <ИмяИсходногоXMLФайла> <ИмяВыходногоPascalФайла> [<ИмяФайлаДляСохраненияXSDСхемы>]");
}
}

private static void ГенерацияВыходногоPascalФайла(Тэг корень, string имяФайла)
{
// Получаем имя модуля по названию pascal файла
имяМодуля = new FileInfo(имяФайла).Name.ToLower().Replace(".pas", "");
// Делаем первую букву названия модуля заглавной
имяМодуля = имяМодуля.Substring(0, 1).ToUpper() + имяМодуля.Substring(1);
using (StreamWriter wr = new StreamWriter(имяФайла, false, Encoding.GetEncoding("Windows-1251")))
{
wr.Write(Header);
wr.WriteLine("{ Декларация интерфейсов }");
foreach (Тэг t in Тэг.Список)
{
if (t.ПростойТип) continue;
wr.WriteLine(" " + t.Заголовок_Interface);
if (t.Повторяется) wr.WriteLine(" " + t.Заголовок_InterfaceList);
}
wr.WriteLine();
foreach (Тэг t in Тэг.Список)
{
if (t.ПростойТип) continue;
wr.WriteLine(t.Описание_Interface);
if (t.Повторяется)
wr.WriteLine(t.Описание_InterfaceList);
}
ДекларацияТипов(wr);

foreach (Тэг t in Тэг.Список)
{
if (t.ПростойТип) continue;
wr.WriteLine("{ " + t.TypeName + " }");
wr.WriteLine();
wr.WriteLine(t.TXMLTypeDec);

if (t.Повторяется)
{
wr.WriteLine(
@"
{ " + t.TypeName + @"List }

" + t.TypeName + @"List = class(TXMLNodeCollection, " +
t.InterfaceName + @"List)
protected
{ " + t.InterfaceName + @"List }
function Add: " +
t.InterfaceName + @";
function Insert(const Index: Integer): " + t.InterfaceName +
@";
function Get_Item(Index: Integer): " + t.InterfaceName + @";
end;
");
}
}

wr.WriteLine(корень.GlobalFunctionsDecl);
wr.WriteLine();
wr.WriteLine(@"const
TargetNamespace = '';");
wr.WriteLine();
wr.WriteLine("implementation");
wr.WriteLine();
wr.WriteLine(корень.GlobalFunctionsImplementation);
foreach (Тэг t in Тэг.Список)
{
if (t.ПростойТип) continue;

List<Тэг> СписокList = new List<Тэг>();
foreach (Тэг child in t.ВложенныеТэги)
if (child.Повторяется)
СписокList.Add(child);

wr.WriteLine();
wr.WriteLine("{ " + t.TypeName + " }");
wr.WriteLine();
string телоAC = t.ТелоAfterConstruction;
if (телоAC != "")
{
wr.WriteLine("procedure " + t.TypeName + ".AfterConstruction;");
wr.WriteLine("begin");
wr.WriteLine(телоAC);
wr.WriteLine(" inherited;");
wr.WriteLine("end;");
}
foreach (Тэг child in t.ВложенныеТэги)
{
if (child.ПростойТип)
{
wr.WriteLine();
wr.WriteLine("function " + t.TypeName + ".Get_" + child.Id + ": " + child.PascalType + ";");
wr.WriteLine("begin");
if (child.TypeCode == XmlTypeCode.String)
{
wr.WriteLine(" Result := ChildNodes['" + child.имяXML + "'].Text;");
}
else
{
wr.WriteLine(" Result := ChildNodes['" + child.имяXML + "'].NodeValue;");
}
wr.WriteLine("end;");
wr.WriteLine();
wr.WriteLine("procedure " + t.TypeName + ".Set_" + child.Id + "(Value: " + child.PascalType +
");");
wr.WriteLine("begin");
wr.WriteLine(" ChildNodes['" + child.имяXML + "'].NodeValue := Value;");
wr.WriteLine("end;");
}
else
{
if (child.Повторяется)
{
wr.WriteLine(@"
function " + t.TypeName + @".Get_" + child.Id + ": " + child.InterfaceName +
@"List;
begin
Result := F" + child.Id + @";
end;");
}
else
{
wr.WriteLine(@"
function " + t.TypeName + @".Get_" + child.Id + @": " + child.InterfaceName +
@";
begin
Result := ChildNodes['" + child.имяXML + "'] as " + child.InterfaceName +
@";
end;");
}
}
}
foreach (Атрибут a in t.Атрибуты)
{
wr.WriteLine(@"
function " + t.TypeName + @".Get_" + a.Id + @": " + a.PascalType +
@";
begin");
if (a.TypeCode == XmlTypeCode.String)
{
wr.WriteLine(" Result := AttributeNodes['" + a.имяXML + "'].Text;");
}
else
{
wr.WriteLine(" Result := AttributeNodes['" + a.имяXML + "'].NodeValue;");
}
wr.WriteLine(
@"end;

procedure " + t.TypeName + @".Set_" + a.Id + @"(Value: " + a.PascalType +
@");
begin
SetAttribute('" + a.имяXML + @"', Value);
end;");
}
if (t.Повторяется)
{
wr.WriteLine(МетодыРаботыСоСписком(t));
}
}
wr.WriteLine("end.");
}
}

private static void ДекларацияТипов(StreamWriter wr)
{
wr.WriteLine();
wr.WriteLine("{ Декларация типов }");
wr.WriteLine();
foreach (Тэг t in Тэг.Список)
{
if (t.ПростойТип) continue;
wr.WriteLine(" " + t.Заголовок_Type);
if (t.Повторяется)
wr.WriteLine(" " + t.Заголовок_TypeList);
}
wr.WriteLine();
}

private static string МетодыРаботыСоСписком(Тэг тэг)
{
return
@"
{ TXML%id%List }

function TXML%id%List.Add: IXML%id%;
begin
Result := AddItem(-1) as IXML%id%;
end;

function TXML%id%List.Insert(const Index: Integer): IXML%id%;
begin
Result := AddItem(Index) as IXML%id%;
end;

function TXML%id%List.Get_Item(Index: Integer): IXML%id%;
begin
Result := List[Index] as IXML%id%;
end;
"
.Replace("%id%", тэг.Id);
}

private static System.Xml.Schema.XmlSchema ПерваяСхема(XmlSchemaSet schemaSet)
{
foreach (System.Xml.Schema.XmlSchema schema in schemaSet.Schemas())
{
return schema;
}
throw new Exception("Из XML файла не было получено ни одной схемы!");
}

public static XmlSchemaSet ПолучениеСхемыXMLФайла(string ИмяИсходногоXMLФайла)
{
XmlSchemaSet schemaSet;
using (XmlTextReader reader = new XmlTextReader(ИмяИсходногоXMLФайла))
{
XmlSchemaInference schemaInference = new XmlSchemaInference();
schemaSet = schemaInference.InferSchema(reader);
}
return schemaSet;
}

private static void ЗаписатьXSDСхемуВФайл(System.Xml.Schema.XmlSchema schema, string ИмяФайлаДляСохраненияXSDСхемы)
{
using (XmlTextWriter writer = new XmlTextWriter(ИмяФайлаДляСохраненияXSDСхемы,
Encoding.GetEncoding("Windows-1251")))
{
writer.Indentation = 2;
writer.IndentChar = ' ';
writer.Formatting = Formatting.Indented;
schema.Write(writer);
}
}

public static Тэг Build(XmlSchemaObject schemaObject)
{
if (schemaObject == null) return null;
Тэг тэг;
Type type = schemaObject.GetType();
switch (type.Name)
{
case "XmlSchemaElement":
XmlSchemaElement element = (XmlSchemaElement) schemaObject;
тэг = Build(element.ElementSchemaType);
if (тэг == null)
{
тэг = new Тэг();
тэг.ПростойТип = true;
}
тэг.имяXML = element.Name;
тэг.TypeCode = element.ElementSchemaType.TypeCode;
тэг.Повторяется = (element.MaxOccursString == "unbounded");
break;
case "XmlSchemaComplexType":
XmlSchemaComplexType complexType = (XmlSchemaComplexType) schemaObject;
тэг = Build(complexType.Particle);
if (тэг == null)
{
тэг = new Тэг();
}
foreach (XmlSchemaAttribute schemaAttribute in complexType.Attributes)
{
Атрибут атрибут = new Атрибут();
атрибут.имяXML = schemaAttribute.Name;
атрибут.TypeCode = schemaAttribute.AttributeSchemaType.TypeCode;
тэг.Атрибуты.Add(атрибут);
}
break;
case "XmlSchemaSequence":
XmlSchemaSequence schemaSequence = (XmlSchemaSequence) schemaObject;
тэг = new Тэг();
foreach (XmlSchemaObject item in schemaSequence.Items)
{
тэг.ВложенныеТэги.Add(Build(item));
}
break;
case "XmlSchemaSimpleType":
return null;
default:
string text = "case \"" + type.Name + "\":\n " + type.Name + " x = (" + type.Name + ") schemaObject;\n" +
" break;";
Clipboard.SetText(text);
throw new Exception(text);
}
if (тэг == null) throw new Exception("Не сработал switch! " + schemaObject);
return тэг;
}

public static string toTranslit(string str)
{
Dictionary<char, string> t = new Dictionary<char, string>();
t.Add('а', "a"); t.Add('б', "b"); t.Add('в', "v"); t.Add('г', "g");
t.Add('д', "d"); t.Add('е', "e"); t.Add('ж', "zh"); t.Add('з', "z");
t.Add('и', "i"); t.Add('й', "y"); t.Add('к', "k"); t.Add('л', "l");
t.Add('м', "m"); t.Add('н', "n"); t.Add('о', "o"); t.Add('п', "p");
t.Add('р', "r"); t.Add('с', "s"); t.Add('т', "t"); t.Add('у', "u");
t.Add('ф', "f"); t.Add('х', "h"); t.Add('ц', "c"); t.Add('ч', "ch");
t.Add('ш', "sh"); t.Add('щ', "sh"); t.Add('ъ', ""); t.Add('ы', "i");
t.Add('ь', ""); t.Add('э', "e"); t.Add('ю', "u"); t.Add('я', "ya");
string res = "";
for (int i = 0; i < str.Length; i++)
{
bool isCapitalLetter = (str[i] >= 'А') && (str[i] <= 'Я');
bool isSmallLetter = (str[i] >= 'а') && (str[i] <= 'я');
if (isCapitalLetter || isSmallLetter)
{
char lowerCase = str[i].ToString().ToLower()[0];
string letter = t[lowerCase];
if (isCapitalLetter && letter.Length >= 1)
{
letter = letter.Substring(0, 1).ToUpper() + letter.Substring(1);
}
res += letter;
}
else
{
res += str[i];
}
}
return res;
}
}
}
</pre>



Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…