logo

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




stden

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


Tags: кодогенерация программирование

Published : 1 year, 7 months ago (Sun, 15 Apr 2007 05:55:41 PDT)
Searched:
http://stden.livejournal.com/319909.html  0 links
Related posts


Встроенный мастер Delphi
Я думаю, каждый, кто использовал XML в Delphi, наверняка использовал мастер XML Data Binding Wizard (хотя, быть может, кто-то писал классы-обёртки для xml вручную, или вообще работал напрямую с компонентом TXMLDocument, но мне лениво :)). Этот мастер позволяет по xml файлу с каким-либо данными получить модуль на языке Pascal для удобной работы с xml файлом:
imageshack.us/my.php?image=file3ti6.png">
Он (в версии для 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>

stden


More results for ""


This is cached version of livejournal post retrieved by LjSEEK on 2007-05-07 05:43:46 . Post may have changed since that time. Click here for actual post version. LjSEEK.COM is not affiliated with author of this post and is not responsible for its content.
These search terms have been highlighted:
Disable Highlighting
stden's Search:
Get your own code!
Copyright © 2005,2006 ljseek.com This service is not affiliated with LiveJournal.com
Design by Steorra.com