| Денис Степулёнок ( @ 2007-04-18 17:11:00 |
| 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
xsd.exe
Выбор средств реализации
На каком языке писать свой мастер:
- на 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/codeg
Исходный код большой и некрасивый :( Протестировано под 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"; }
///
<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:] <-- Генерация <B>xsd</B> файла по <B>xml</B> файлу.
xsd.exe <SCHEMA>.xsd /classes|dataset [/e:] [/l:] [/n:] [/o:] [/s] [/uri:] <-- Генерация классов по <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>