суббота, 18 июня 2011 г.

Быстрый просмотр кода функции на веб странице.

Иногда при изучении веб страницы из сети нужно просмотреть код той или иной JavaScript функции. Это может понадобиться к примеру в том случае, когда нужно посмотреть как реализована та или иная функция страницы или, например, при извлечении данных со страницы можно столкнуться с тем, что интересующая часть содержимого генерируется с помощью сценария. В этих случаях обычно несложно установить имя функции, которая реализует интересующий функционал, но найти ее в многочисленный скриптах самой страницы, а так же тех, которые подключены к ней(а тем более подключаемых динамически), оказывается не так просто.

В Firefox проблему можно решить с помощью расширений, позволяющих выполнять собственный код, таких как Custom buttons или iMacros. Можно найти нужную функцию программно по ее имени и отобразить код в окне сообщений(alert), благо в последней версии браузера в окне сообщений есть возможность выделить и скопировать сообщение.

Сделать это можно при помощи следующего кода:

  1: function getContentWin() 
  2: {  
  3: var cont = getBrowser().contentWindow;  
  4: try 
  5:   {    
  6:     cont = new XPCNativeWrapper(cont).wrappedJSObject;
  7:   } 
  8:   catch(e) {}  
  9:   return cont;
 10: }
 11: var _window = getContentWin();
 12: var funcName = _window.prompt("Введите имя функции, код которой следует отобразить");
 13: _window.alert(_window[funcName].toString());

При его запуске сначала появляется окошко в которое надо ввести имя интересующей функции( с учетом регистра), а после подтверждения появится окно с кодом функции.

пятница, 10 июня 2011 г.

AutoCode

Скачав данный инструмент, я был в таком восторге, что решил о нем написать. В галерее расширений Visual Studio было написано, что он бесплатный, на сайте разработчика, что распространяется по лицензии Donate, но отложив написание обзора на некоторое время и снова к нему вернувшись впоследствии, я вдруг обнаружил, что это обычные шаровары(Shareware) и те функции, которые мне понравились больше всего более недоступны. Продолжить написание обзора уже было невозможно, поэтому я это забросил, но потом решил опубликовать, не выкидывать же в конце-то концов. Публикую черновик в неизменном виде(на чем останвоился).

 

Недавно, просматривая расширения для Visual Studio, наткнулся на этот замечательный инструмент. Поскольку вопросы кодогенерации меня интересуют давно, я сразу обратил на него внимание. Покопавшись в его возможностях некоторое время, решил поделиться впечатлениями.

AutoCode представляет из себя инструмент генерации кода любого уровня сложности. В отличие от Code Snippets( инструмента, входящего в состав Visual Studio), он позволяет не просто вставлять готовый шаблон, а выполнять программный код, генерирующий все, что можно себе только вообразить. Кроме того, в состав готовых команд входят и такие, которые просто выполняют команды “студии”. И хотя ценность таких команд невелика, тем не менее факт их наличия демонстрирует возможности инструмента, которые выходят за рамки простой кодогенерации, а имеют полный доступ модели автоматизации(Visual Studio Automation Model).

В демо-ролике AutoCode, а так же в начальных сведениях на сайте разработчика есть пример с классом Person, в котором, написав всего несколько слов, можно создать код класса с заданным набором свойств.

Пишем в коде

int id string name Person class

Размещаем каретку в конце строки, нажимаем Ctrl+Enter(другой вариант – нажать Ctrl+Enter сразу и вводить строку в открывшемся окошке, затем нажимать просто Enter, таким образом можно получить список доступных команд) и в результате получаем код

1: public class Person
2: {
3:     private int _id;
4:     private string _name;
5:     public Person()
6:     {
7:     }
8:     public int Id
9:     {
10:         get { return _id; }
11:         set { _id = value; }
12:     }    public string Name
13:     {
14:         get { return _name; }
15:         set { _name = value; }
16:     }
17: }

В данном случае class – это имя команды, а все, что ему предшествует – аргументы. Последний аргумент – имя класса, а все предшествующие должны быть парными и в каждой паре первый элемент – имя типа, второй – имя свойства. Таких пар аргументов(тип-имя) может быть достаточно много, правда все должны уместиться на одной строке.


Инструмент можно использовать не только в C#, но и в любых других документах Visual Studio, это еще одно выгодное отличие AutoCode от Code Snippet, который может работать с ограниченным количеством типов документов. Однако большинство шаблонов, генерирующих код, способны генерировать его для конкретного языка и обычно бывают доступны в файлах с кодом, написанном именно на этом языке.


Меня гораздо больше интересует генерация кода на VB.Net нежели на C#, но в наборе команд для этого языка нет ничего подобного тому, что было продемонстрировано в демо-ролике. Однако вся прелесть AutoCode в том, что в нем как и в Code Snippets можно создавать собственные команды. Поэтому первым чем я занялся в моем изучении возможностей этого инструмента, было создание команды для VB.Net, подобной команде class для C#.


Команда представляет из себя XML файл с расширением .autox. Описание формата можно найти здесь. Команды размещаются в отдельной папке при установке AutoCode. Обычно это папка расположена в  папке Документы (или Мои документы) и называется AutoCode 2010 (для посленей версии, естественно). Собственные команды можно размещать в подкаталоге Commands/(My Commands). Установив в этот каталог свою команду, можно автоматически получить к ней доступ( если Visual Studio запущена, то фактически команда будет доступна после обновления AutoCode, для этого надо выполнить команду ref, введя ее в окно команд).


Создание команды class для VB.Net



Итак создаем команду, для этого в указанном каталоге создадим текстовый файл  vbClass.autox. Затем введем в него следующий код

1: <?xml version="1.0" encoding="utf-8" ?>
2: <Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
3:   <Command name="vbClass" version="1.0">
4:     <CommandBehavior>
5:       <CommandLine shortcut="class"/>
6:       <ActiveDocument required="true" extensions=".vb"/>
7:     </CommandBehavior>
8:     <CommandInfo>
9:       <Category>Statements</Category>
10:       <Author>diadiavova</Author>
11:       <LanguageCategory>VB.NET</LanguageCategory>
12:       <Description>
13:         Определяет класс с набором свойств.
14:       </Description>
15:       <Usage>                
16:         <![CDATA[<Type1> <Name1>...<className> class]]>
17:       </Usage>
18:     </CommandInfo>
19:     <CommandCode language="vb">
20:       <Codes>
21:         <Code codeElement="Selection" codePoint="StartOfSelection">
22:           <![CDATA[
23:           Public Class <#=args(args.Length - 1)#>    
24:             <# For i As Integer = 0 To args.Length - 2 Step 2 #>
25:             dim _<#= args(i + 1) #> As <#= args(i) #>
26:             Public Property    <#= args(i + 1) #> As <#= args(i) #>
27:               Get
28:                 Return _<#= args(i + 1) #>            
29:               End Get            
30:               Set(ByVal value As <#= args(i) #>)                
31:                 _<#= args(i + 1) #> = value
32:               End Set        
33:             End Property    
34:             <# Next #>    
35:           End Class                
36:           ]]>                
37:         </Code>
38:       </Codes>
39:     </CommandCode>
40:   </Command>
41: </Commands>

Сохраним документ, после чего в студии вызовем окно AutoCode(Ctrl+Enter), наберем ref и нажмем Enter.


Тепрь в коде VB.Net можно ввести

integer Id string Name Person class


далее Ctrl+Enter и получим код

1: Public Class Person
2: 
3:     Dim _Id As Integer
4: 
5:     Public Property Id As Integer
6:         Get
7:             Return _Id
8:         End Get
9:         Set(ByVal value As Integer)
10:             _Id = value
11:         End Set
12:     End Property
13: 
14:     Dim _Name As String
15: 
16:     Public Property Name As String
17:         Get
18:             Return _Name
19:         End Get
20:         Set(ByVal value As String)
21:             _Name = value
22:         End Set
23:     End Property
24: End Class
Рассмотрим документ немного подробнее. С корневым элементом, как я полагаю, вопросов нет, при установке AutoCode так же устанвливается схема документа, так что при указании пространства имен студия будет выдавать подсказки для элементов и атрибутов.
<Command name="vbClass" version="1.0">

Здесь указывается полное имя команды и версия. Полное имя должно быть более понятным чем шорткат, оно выводится в окнах программы и по нему должно быть понятно, что делает команда. Окна вызываются сочетаниями клавиш Ctrl+Enter(окно IntelliSense) и Ctrl+0(каталог команд). При выделении команды в них выводится информация об этой команде.

<CommandLine shortcut="class"/>


Здесь указывается сочетание символов, с помощью которого команда вызывается.

<ActiveDocument required="true" extensions=".vb"/>


Атрибут extensions указывает расширения имен файлов документов, в которых доступна команда. Атрибут required указывает, является ли требование к расширению обязательным: если имеет значение true, то команда будет недоступна в других документах. Это полезно, когда команда предназначена для конкретного языка, но в то же время, в других языках может существовать одноименная команда(как в данном случае мы создали для бейсика команду, аналогичную той, которая существует для шарпа и определили для нее такой же шорткат).

<LanguageCategory>VB.NET</LanguageCategory>


Указывает в папке какого языка команда будет отображаться в каталоге команд(Ctrl+0).

<Category>Statements</Category>


Путь к команде в каталоге. Например в данном случае наша команда будет отображаться в разделе VB.NET/Statements, если надо, чтобы она отображалась в более глубоком подразделе(например, если мы захотим разместить ее в разделе VB.NET/Statements/Custom), то разделять подразделы надо слешем, как показано ниже.



<Category>Statements/Custom</Category>



По поводу элементов Author, Description, Usege, а так же Example и HelpUrl, как я полагаю вопросов возникнуть не должно, так что останавливаться на ни подробно не стану.

<CommandCode language="vb">


В этом элементе собственно и создается код. Атрибут language указывает на каком языке будет описана логика генерации кода. При этом результат может оказаться кодом любого языка и быть доступен где угодно, но логика данной команды описывается именно на том языке, который указан в этом атрибуте.

<Code codeElement="Selection" codePoint="StartOfSelection">


Таких элементов может быть несколько, это может понадобиться, например, когда надо обернуть выделенную часть текста, скажем болоком #Region или в других подобный случаях. Для этого атрибуты указывают где и как должен вставляться код. Вариантов много, так что подробно останавливаться не буду.



Ну и наконец сам код(содержимое элемента Code). Для генерации кода используется синтаксис текстовых шаблонов (T4), Сам код заключается в блок CDATA, поскольку содержит символы < и >. Можно использовать маркеры <# #> и <#= #>, остальные игнорируются. Если описать совсем кратко формат шаблонов, то все это маркеры по своему действию похожи на маркеры <%%> и <%=%> в ASP.Net. Аргументы, передаваемые команде, содержатся в массиве args.





Шаблоны в отдельных файлах



Среди доступных команд, поставляемых с AutoCode есть команда ax. Эта команда создает заготовку для новой команды, в которую можно добавить собственную логику и описание, сохранить и пользоваться. Для ее выполнения надо выполнить строку



<ИмяНовойКоманды> ax



При этом в нужном каталоге создается два новых файла с именами, соответствующем введенному имени новой команды и расширениями .autox  и .txt. В элементе Code можно ничего не писать, а просто сослаться на текстовый файл.

<Code file=""/>


Фактически содержимое текстового файла вставляется в то место, где раньше у нас был блок CDATA. Редактировать шаблон в отдельном файле удобно, но, если это простой текстовый файл, пользы будет немного. Проблема в том, что шаблон содержит программный код, смешанный с текстом и маркерами, отделяющими одно от другого. Хорошо было бы, если бы программный код подсвечивался как в редакторе кода, маркеры – каким-то особым образом и было сразу видно, где код, а где текст.  Кроме того, неплохо было бы получить поддержку IntelliSence для кода. Все описанные возможности предоставляет расширение для Visual Studio, которое называется tangible T4 Editor. Поискав, я нашел еще Visual T4, но я его пока не пробовал, так что не знаю насколько он хорош.



Подсветка и другие удобства доступны в файлах с расширением .tt, а не .txt. Это не проблема, поскольку элемент Include может ссылаться на файл с любым расширением, а вот с использованием команды ax есть небольшая проблема. Здесь придется либо создавать tt файл вручную в нужном каталоге, либо потребуется собственная команда(аналогичная ax, но создающая вместо текстового файла, файл текстового шаблона). Полагаю, гораздо удобнее будет именно второй вариант, этим мы сейчас и займемся.


Для начала нам нужно будет изучить код команды ax, потом отредактировать его и сохранить отредактированную копию, как отдельную команду. Для этого надо либо найти файлы команды, либо выполнить команду open. Синтаксис ее прост:


<ИмяКоманды> open


То есть в нашем случае надо выполнить


ax open


и файлы откроются в студии. Код всех встроенных команд так же можно увидеть в галерее кода на сайте программы, в частности код команды ax находится здесь.


Открыв документы лучше сразу сохранить их копии в каталоге (My Commands) с новыми именами, чтобы случайно не изменить оригиналы. Я использовал для всех файлов имя autocodett с соответствующими расширениями.


Здесь помимо autox и txt файлов мы еще имеем cs файл. Вообще все файлы, из которых состоит команда собираются в один класс команды ( об этом я напишу чуть ниже). В autox файле содержатся ссылки на все файлы из которых строится класс команды. Текстовые шаблоны(как уже говорилось выше) добавляются посредством атрибута file элемента Code, а файлы с кодом класса с помощью аналогичного атрибута элемента Include


    <Includes>
      <
Include file="AutoCodeTT.cs"/>
    </
Includes>
Эти ссылки я изменил в соответствии с новыми именами файлов. В текстовом файле содержится шаблон autox файла для новой команды, там я внес небольшие изменения, например сменил язык написания кода новой команды на бейсик., а в cs файле поменял в коде расширение для имени файла создаваемого текстового файла с txt на tt, дописал туда заголовок шаблона, чтобы T4 editor поддерживал VB.Net

<#@ template language="VB" #>
И тому подобные мелкие изменения. Вот что получилось
autocodett.autox


1: <?xml version="1.0"?>
2: <Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
3:   <Command name="AutoCodeTT" version="1.0">
4:     <Includes>
5:       <Include file="AutoCodeTT.cs"/>
6:     </Includes>
7:     <CommandBehavior>
8:       <CommandLine shortcut="axtt" />
9:       <ActiveDocument required="false" extensions="*" />
10:     </CommandBehavior>
11:     <CommandInfo>
12:       <LanguageCategory>Common</LanguageCategory>
13:       <Category>MyCode</Category>
14:       <Usage>
15:         <![CDATA[(Select Text) <name> <shortcut> ax]]>     
16:       </Usage>
17:       <Example>MyCustomCommand mcc ax</Example>
18:       <Description>
19:         Creates a custom AutoCode Command in 
20:         "My Documents\AutoCode" folder with tt-template. 
21:         By default, the command will emit the selected text.
22:       </Description>
23:       <Author>DevProjects</Author>
24:       <HelpUrl>http://help.devprojects.net/gallery/command/autocode</HelpUrl>
25:     </CommandInfo>
26:     <CommandCode language="csharp">
27:       <Codes>
28:         <Code id="Template" file="autocodett.txt" />
29:       </Codes>
30:       <Selection codeElement="Template" codePoint="StartOfElement">
31:         <SelectText><![CDATA[<#=Parameters["Text"]#>]]></SelectText>
32:       </Selection>
33:     </CommandCode>
34:   </Command>
35: </Commands>

autocodett.cs

1: using System;
2:     using System.IO;
3:     using DevProjects.AutoCode.Commands;
4:     public partial class AutoCodeTTCommand : CommandBase
5:     {
6:         protected override bool BeforeExecute(string commandArgs)
7:         {
8:             if (Arguments.Length < 2)
9:             {
10:                 CommandHelper.InvalidArguments("Please, specify the name and shortcut of the command to be created.");
11:                 return false;
12:             }
13:             Parameters["CommandName"] = Arguments[0];
14:             Parameters["Shortcut"] = Arguments[1];
15:             Parameters["Text"] = "<#@ template language=\"VB\" #>\r\n" +
16:                 (String.IsNullOrEmpty(SelectedText) ? "Insert code here." : SelectedText);
17:             return true;
18:         }
19:         protected override void Execute(string commandArgs)
20:         {
21:             string path = Path.Combine(Catalog.CurrentUserPath, "(My Commands)");
22:             if (!Directory.Exists(path))
23:             {
24:                 Directory.CreateDirectory(path);
25:             }
26:             string txtFileName = DTEHelper.GetUniqueFileName(path, Parameters["CommandName"] + ".tt");
27:             using (StreamWriter writer = new StreamWriter(txtFileName))
28:             {
29:                 writer.Write(Parameters["Text"]);
30:             }
31:             string autoxFileName = DTEHelper.GetUniqueFileName(path, Parameters["CommandName"] + ".autox");
32:             using (StreamWriter writer = new StreamWriter(autoxFileName))
33:             {
34:                 string name = Path.GetFileName(txtFileName);
35:                 writer.Write(BuildTemplate(name));
36:             }
37:             DTEHelper.OpenFile(autoxFileName);
38:             System.Threading.Thread.Sleep(100);
39:             ActiveDocument.Save(null);
40:             // Save to refresh catalog
41:             System.Threading.Thread.Sleep(100);
42:             DTEHelper.OpenFile(txtFileName);
43:         }
44:     }
autocodett.txt
<?xml version="1.0"?>
<Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
<Command name="<#=Parameters["CommandName"]#>" version="1.0">
<CommandBehavior>
<CommandLine shortcut="<#=Parameters["Shortcut"]#>" />
<ActiveDocument required="false" extensions="*"/>
</CommandBehavior>

<CommandInfo>
<LanguageCategory>Common</LanguageCategory>
<Category>(My Commands)</Category>
<Usage><#=Parameters["Shortcut"]#></Usage>
<Description>No description supplied.</Description>
<Author>Me</Author>
<HelpUrl></HelpUrl>
</CommandInfo>

<CommandCode language="vb">

<Codes>
<Code id="Template1" file="<#=args[0]#>" />
</Codes>

<SmartFormat>
<Start codeElement="Template1" codePoint="StartOfElement" />
<End codeElement="Template1" codePoint="EndOfElement" />
</SmartFormat>

<Selection codeElement="Template1" codePoint="EndOfElement">
<SelectText></SelectText>
</Selection>

</CommandCode>
</Command>
</Commands>



Теперь попробуем создать новую команду с помощью созданной только что команды. Создадим команду ProppertyList, которая будет генерировать код для набора свойств одного типа. Для этого выполним команду



PropertyList proplist axtt



В текстовый шаблон введем  следующий код:

<#@ template language="VB"   #>

<# For i As Integer = 1 To args.Length - 1 #>
Dim _<#= args(i) #> As <#= args(0) #>
Public Property <#= args(i) #>() As <#= args(0) #>
Get
Return _<#= args(i) #>
End Get
Set(ByVal value As <#= args(0) #>)
_<#= args(i) #> = value
End Set
End Property

<# Next #>


В autox файле потребуется только сменить данные описывающие команду, у меня это выглядит так:

1: <?xml version="1.0"?>
2: <Commands xmlns="http://schemas.devprojects.net/AutoCode/v4.0">
3:   <Command name="PropertyList" version="1.0">
4:     <CommandBehavior>
5:       <CommandLine shortcut="proplist" />
6:       <ActiveDocument required="true" extensions=".vb"/>
7:     </CommandBehavior>
8:     <CommandInfo>
9:       <LanguageCategory>VB.NET</LanguageCategory>
10:       <Category>Members/Properties</Category>
11:       <Usage><![CDATA[<type> <name1> <name2>...]]>proplist</Usage>
12:       <Description>Создает набор свойств одного типа.</Description>
13:       <Author>diadiavova</Author>
14:       <HelpUrl></HelpUrl>
15:     </CommandInfo>
16:     <CommandCode language="vb">
17:       <Codes>
18:         <Code id="Template1" file="PropertyList.tt" />
19:       </Codes>
20:       <SmartFormat>
21:         <Start codeElement="Template1" codePoint="StartOfElement" />
22:         <End codeElement="Template1" codePoint="EndOfElement" />
23:       </SmartFormat>
24:       <Selection codeElement="Template1" codePoint="EndOfElement">
25:         <SelectText></SelectText>
26:       </Selection>
27:     </CommandCode>
28:   </Command>
29: </Commands>


Сохраняем файлы(autox файл надо сохранять последним, поскольку изменения других файлов сохраненные после сохранения главного файла могут оказаться недоступными, может так же понадобиться выполнение команды ref, хотя вряд ли это будет необходимо, поскольку после сохранения autox файла программа autocode обновляет команды), и выполняем в коде класса Person следующее:

string SecondName Surname Email proplist

Синтаксис команды прост: сначала указываем тип всех свойство, потом имена, которые и, наконец, имя шорткат команды.




И в коде класса появится следующее:

1: Dim _SecondName As String
2: Public Property SecondName() As String
3:     Get
4:         Return _SecondName
5:     End Get
6:     Set(ByVal value As String)
7:         _SecondName = value
8:     End Set
9: End Property
10: 
11: Dim _Surname As String
12: Public Property Surname() As String
13:     Get
14:         Return _Surname
15:     End Get
16:     Set(ByVal value As String)
17:         _Surname = value
18:     End Set
19: End Property
20: 
21: Dim _Email As String
22: Public Property Email() As String
23:     Get
24:         Return _Email
25:     End Get
26:     Set(ByVal value As String)
27:         _Email = value
28:     End Set
29: End Property

четверг, 9 июня 2011 г.

Экспорт данных из DataTable(ADO.Net) с помощью XSLT. Часть 5–файлы и ссылки.

 

В предыдущих четырех частях я описал экспорт данных содержащихся в XML документе, в несколько форматов. Естественно, это не все, что можно написать по этому поводу, поскольку тема эта неисчерпаема, но описать все и не было цели. Здесь хочу собрать ссылки на все статьи по теме и файлы.

Статьи об экспорте в:

  1. HTML
  2. Excel CSV
  3. Excel XML
  4. RTF

Кроме того, учитывая что примеры кода, приведенные в тексте, для копипасты не годятся, поскольку там пронумерованы строки, я собрал все документы в архив, который можно скачать здесь. Там есть как сами файлы с кодом, так и результаты преобразований.

Базу NORTHWIND, которую я использовал в примерах, можно скачать отсюда.

На тему преобразования XML в RTF с помощью Visual Basic и ASP.Net нашел на MSDN статью, там в конце есть ссылки по теме. Сам статью не читал, так что не знаю что там. И там еще в конце есть несколько ссылок.

среда, 8 июня 2011 г.

Экспорт данных из DataTable(ADO.Net) с помощью XSLT. Часть 4–RTF.

 

Иногда требуется, чтобы документ был представлен в каком-то редактируемом формате, но не всегда можно быть уверенным в том, что на компьютере, где он будет просматриваться или редактироваться есть необходимое для этих целей программное обеспечение. Обычно для целей редактирования используются офисные пакеты, но не на всех компьютерах они есть. Между тем в операционной системе Windows присутствует программа WordPad, способная отображать довольно сложное содержимое. И хотя возможности ее скромны, тем не менее, она иногда бывает довольно полезной. Вставить в документ таблицу, пользуясь средствами этой программы нельзя(там нет инструментов для работы с таблицами), а вот отобразить и редактировать ее содержимое – вполне.

Основным форматом, с которым работает эта программа является RTF(Rich Text Format), который так же является одним из основных форматов программы MS Word, да и вообще операционной системы Windows, где этот формат является форматом буфера обмена(то есть, если даже скопировать что-то на веб-странице в буфер, то потом можно извлечь оттуда скопированное в RTF).

Как это ни странно, но формат этот не бинарный, а текстовый и документ в этом формате вполне может быть прочитан человеком и даже создан в любом текстовом редакторе, включая блокнот. Единственная (ну почти) проблема этого формата – это то, что он слишком сложен и по этой причине непосредственно с ним работают только программы, а писать код на нем – не есть хорошо.

Рассмотрим простой пример. Создадим в программе WordPad документ следующего содержания

Hello, world!
Preved.

Сохраним его и откроем файл в Блокноте. Текст будет выглядеть следующим образом

{\rtf1\ansi\ansicpg1251\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
{\colortbl ;\red255\green0\blue0;}
{\*\generator Msftedit 5.41.21.2509;}\viewkind4\uc1\pard\sa200\sl276\slmult1\b\f0\fs22 Hello,\b0  \cf1 world\cf0 !\par
\ul Preved\ulnone .\par
}

При беглом взгляде – полная абракадабра, но если присмотреться, то начинает усматриваться смысл и даже не зная формата можно рассмотреть некоторые закономерности. Весь код заключен в фигурные скобки, так же внутри есть отдельные блоки, которые так же заключены в фигурные скобки. Спецсимволы начинаются с обратного слэша, обычно представляются либо алфавитно-цифровыми символами причем сначала идут буквы, потом индексы, хотя индексы не всегда нужны. Так же могут встречаться одиночные не алфавитно-цифровые символы. (например \*). Такие обозначения как ansi, ansicpg1251? deflang1033, как мне кажется не нуждаются в пояснениях, далее идет два интересных блока, а именно таблица шрифтов и цветовая таблица. Долго о них тоже рассуждать нет смысла, скажу лишь, что в тексте будут ссылки на эти таблицы и индексы цветов и шрифтов. В нашем документе используется только один шрифт и один(отличный от черного) цвет, а вообще, когда их больше, то ссылки на них имеют следующий вид

\cf2 – обозначает, что цвет шрифта второй в таблице
\f3 – шрифт третий в таблице.

То есть ничего сложного. Далее в самом тексте документа можно увидеть слова, которые мы ввели в документ и эти слова окружены группами дескрипторов rtf, некоторые из которых без спецификации непонятны, с другими нет никаких проблем. Перед словом Hello можно увидеть среди прочих \b\f0, нетрудно догадаться, что \b означает жирный текст, а \f0 – самый первый шрифт в таблице, там в таблице он и обозначен так же. Сразу после этого слова мы видим \b0  \cf1. Так же нетрудно понять, что \b0 отменяет жирный шрифт, а \cf1 назначает красный цвет(он первый в таблице, нулевым будет черный, это цвет по умолчанию). Сразу после мы видим \cf0 – возврат к черному цвету. Строка завершается дескриптором \par. Последнее слово окружено дескрипторами \ul и \ulnone, которые означают начало и конец подчеркнутого текста.

Есть правда одна проблема: формат не понимает русского языка, равно как и любого другого, кроме английского. Если создать документ с русским текстом, например со словом “Привет”, то вместо этого слова мы увидим \lang1049\'cf\'f0\'e8\'e2\'e5\'f2. В данном случае первый дескриптор означает русский язык, а далее, каждый символ кодируется с помощью \’n , где n – шестнадцатиричное представление символа в кодировке ansi. Это приводит к тому, что для создания преобразований, в которых будет текст не на английском языке, нам понадобится включать в них скрипты, поскольку средствами XSLT такое преобразование не выполнить.

Создание документа нужной структуры

Теперь, имея первоначальные сведения о формате, можно перейти к процессу построения документа. Естественно, не зная достаточно хорошо формата, а только научившись понимать некоторые его элементы, невозможно “с нуля” построить документ, но это, в принципе, и не понадобится. Можно, конечно, изучить спецификации, ссылки на них есть в статье в википедеии, посвященной RTF, но, как я полагаю, достаточного беглого взгляда на эти документы, чтобы такое желание пропало. Я опишу несколько другой подход, а именно: макет документа будет создаваться программами, умеющими работать с RTF, а вот данные, которые мы хотим отобразить, мы будем формировать в своем приложении, а точнее в XSL-преобразовании.

Как и при рассмотрении экспорта в другие форматы, данные будем отображать в виде таблицы. Для этого нам понадобится RTF документ с примером таблицы, какую мы хотим получить. Как я уже упоминал выше, WordPad не умеет работать с таблицами, поэтому для создания документа надо воспользоваться программой Word и при сохранении документа выбрать формат сохранения. Для самой таблицы можно подобрать стиль(так интереснее), но вместо тестовых данных лучше ввести какие-нибудь метки, которые потом легко будет найти в документе, я обычно пишу одним словом и латинскими буквами, что означает данный текст и обрамляю это слово каким-то заметным образом, например по три звездочки с обеих сторон, ну что-то типа ***documentHeader***. Заведомо не будет проблем с кодировкой, легко найти и понять, что данный элемент означает.

Итак, открываем Word, создаем таблицу 3х3, можно выбрать или настроить стиль, включаем надписи, чтобы было видно где что. У меня получилась вот такая таблица-шаблон, с чередующимся стилем строк.

Таблица

Сохраняем документ в формате RTF. Здесь надо сказать, что RTF, создаваемый Word’ом – это не совсем то, что нам нужно. Эта программа включает много лишнего в документ, эти элементы содержат информацию нужную программе, но для правильного отображения они не нужны, а это значит, что разбираться в документе будет сложнее и сложности эти нам ни к чему. Для того, чтобы минимизировать документ мы откроем его в программе WordPad и снова сохраним.

В результате этих нехитрых манипуляций получается примерно следующее(дополнительные пустые строки я вставил для наглядности, на отображение это не влияет.

{\rtf1\ansi\ansicpg1251\deff0\deflang1049\deflangfe1049\deftab708{\fonttbl{\f0\froman\fprq2\fcharset204{\*\fname Times New Roman;}Times New Roman CYR;}{\f1\fswiss\fprq2\fcharset204 Calibri;}}
{\colortbl ;\red255\green255\blue255;\red0\green0\blue0;\red0\green255\blue255;\red217\green217\blue217;}
{\*\generator Msftedit 5.41.21.2509;}\viewkind4\uc1\trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 \trbrdrt\brdrs\brdrw10 \trbrdrr\brdrs\brdrw10 \trbrdrb\brdrs\brdrw10 \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3

\clcfpat2\clcbpat1\clshdng10000\cellx1843
\clcfpat2\clcbpat1\clshdng10000\cellx5387
\clcfpat2\clcbpat1\clshdng10000\cellx7797

\pard\intbl\qc\cf1\b\f1\fs22 ***Header1***\cell ***Header2***\cell ***Header3***\cell\row

\trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 \trbrdrt\brdrs\brdrw10 \trbrdrr\brdrs\brdrw10 \trbrdrb\brdrs\brdrw10 \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3

\clcbpat1\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs\cellx1843
\clcbpat1\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs\cellx5387
\clcbpat1\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx7797

\pard\intbl\cf0\b0 ***R1C1***\cell ***R1C2***\cell ***R1C3***\cell\row

\trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 \trbrdrt\brdrs\brdrw10 \trbrdrr\brdrs\brdrw10 \trbrdrb\brdrs\brdrw10 \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3

\clcbpat4\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx1843
\clcbpat4\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx5387
\clcbpat4\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx7797

\pard\intbl ***R2C1***\cell ***R2C2***\cell ***R2C3***\cell\row\pard\sa200\sl276\slmult1\par
}

Текст, который отображается в ячейках таблицы я выделил жирным шрифтом, а дескрипторы, на которые следует обратить внимание в первую очередь – выделил желтым фоном.

Первое, что бросается в глаза – это то, что после каждого значения ячейки идет дескриптор \cell, сообщающий о завершении ячейки, в конце каждой строки мы видим дескриптор \row. Таким образом, для того, чтобы создать таблицу с нужным количеством столбцов, нам надо будет в каждой строке значения определять именно таким образом. Но это еще не все. Помимо значений нам надо будет указать и другую информацию о ячейках. В первую очередь нас интересует ширина каждой ячейки и стиль(цвет фона и цвет шрифта). Без этого правильно отобразить таблицу не получится.

Перед строкой заголовка мы видим

\clcfpat2\clcbpat1\clshdng10000\cellx1843
\clcfpat2\clcbpat1\clshdng10000\cellx5387
\clcfpat2\clcbpat1\clshdng10000\cellx7797

Это описания для каждой ячейки заголовка, опишу значения важных для нас элементов(тех, которые, возможно, придется изменять).

  • \clcfpat2 – означает, что цвет шрифта ячейки в цветовой таблице второй в списке
  • \clcbpat1 – цвет фона ячейки в цветовой таблице первый в списке
  • \celxN – этот параметр влияет на ширину ячейки, но фактически это не ширина, а расстояние между правой границей ячейки и началом строки таблицы. То есть в каждой следующей ячейке этот параметр увеличивается по сравнению с предыдущими. Вместо N – число, указывающее это расстояние в твипах.

Аналогичные определения есть для каждой строки, только для остальных строк отсутствует определение для цвета шрифта, поскольку там используется цвет по умолчанию, ну и дескрипторы, которые мы игнорируем и переносим в документ без изменений тоже немного другие.

Попробуем поэкспериментировать и внесем несколько изменений вручную. Там где описаны определения для заголовка добавим еще одно такое же, только к отступу от начала строки прибавим 2000 твипов

\clcfpat2\clcbpat1\clshdng10000\cellx9797

Аналогичные изменения внесем и в определения других строк. Кроме того в сами строки внесем дополнительные ячейки

\pard\intbl\qc\cf1\b\f1\fs22 ***Header1***\cell ***Header2***\cell ***Header3***\cell ***Header4***\cell\row

И в остальных то же самое. Получится следующий код.

{\rtf1\ansi\ansicpg1251\deff0\deflang1049\deflangfe1049\deftab708{\fonttbl{\f0\froman\fprq2\fcharset204{\*\fname Times New Roman;}Times New Roman CYR;}{\f1\fswiss\fprq2\fcharset204 Calibri;}} {\colortbl ;\red255\green255\blue255;\red0\green0\blue0;\red0\green255\blue255;\red217\green217\blue217;} {\*\generator Msftedit 5.41.21.2509;}\viewkind4\uc1\trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 \trbrdrt\brdrs\brdrw10 \trbrdrr\brdrs\brdrw10 \trbrdrb\brdrs\brdrw10 \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3

\clcfpat2\clcbpat1\clshdng10000\cellx1843 \clcfpat2\clcbpat1\clshdng10000\cellx5387 \clcfpat2\clcbpat1\clshdng10000\cellx7797 \clcfpat2\clcbpat1\clshdng10000\cellx9797

\pard\intbl\qc\cf1\b\f1\fs22 ***Header1***\cell ***Header2***\cell ***Header3***\cell ***Header4***\cell\row

\trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 \trbrdrt\brdrs\brdrw10 \trbrdrr\brdrs\brdrw10 \trbrdrb\brdrs\brdrw10 \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3

\clcbpat1\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs\cellx1843 \clcbpat1\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs\cellx5387 \clcbpat1\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx7797 \clcbpat1\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx9797

\pard\intbl\cf0\b0 ***R1C1***\cell ***R1C2***\cell ***R1C3***\cell ***R1C4***\cell\row

\trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 \trbrdrt\brdrs\brdrw10 \trbrdrr\brdrs\brdrw10 \trbrdrb\brdrs\brdrw10 \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3

\clcbpat4\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx1843 \clcbpat4\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx5387 \clcbpat4\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx7797 \clcbpat4\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs \cellx9797

\pard\intbl ***R2C1***\cell ***R2C2***\cell ***R2C3***\cell ***R2C4***\cell\row\pard\sa200\sl276\slmult1\par }

Сохраним изменения и откроем в WordPad или Word. Вот, что получилось у меня.

TableEdited

То есть именно то, что и ожидалось. Теперь мы знаем вполне достаточно для того, чтобы перейти к построению преобразования XML таблицы в RTF.

XSL преобразование

Весь процесс состоит в том, чтобы разделить изготовленный шаблон на неизменяемые части и их перенести в преобразование в виде текстовых блоков, а изменяемые сгенерировать и вставить в нужные места. Но для начала надо оговорить еще один момент, о котором я уже упоминал. Речь о всевозможных нелатинских символах. Основные символы из набора(символы 0-128 всех кодировок) могут передаваться как есть за несколькими исключениями. Ну к примеру, обратный слеш используется как спецсимвол и будет интерпретирован именно так, поэтому для того, чтобы он отобразился в документе его надо удвоить, Другой пример – фигурная скобка, так же используется в разметке, поэтому для ее отображения надо ее экранировать обратным слешем(\{ или \}). Здесь придется внимательно изучить какие символы как должны представляться, выискивать их и заменять. С символами других языков еще больше мороки, поскольку придется проверять какому именно алфавиту они принадлежать и указывать это в разметке, хорошо, если это один язык и заранее известно какой именно. Хотя, если все символы, предназначенные для отображения передавать в виде управляющих последовательностей, то первая проблема( с экранированием спецсимволов) решается автоматически. Такой код не очень компактен, поскольку придется писать по четыре символа для отображения только одного(в случае латинских букв и символов основного набора), зато реализовать проще. Проблему с мультиязычными документами, а так же случаями, когда неизвестно заранее какие языки будут использоваться, можно решить с помощью еще одного способа, а именно последовательности для отображения символов Юникода. К сожалению этот способ еще более громоздкий, поскольку для вывода одного символа потребуется написать до 8-ми, тем не менее он самый надежный и простой в реализации. В любом случае, выбирать следует исходя из ситуации: если размер файла не критичен, то можно отображать символы юникода; если критичен, то лучше воспользоваться всеми способами понемногу. Например латиницу отображать как есть, выбранный язык – при помощи ANSI-последовательностей, а остальное – Юникодом. Символ Юникода отображается так \uN? . Здесь синяя часть должна присутствовать как есть, красная –  код символа в Юникоде(в десятичной системе), в конце символ, который будет отображаться программами, не поддерживающими Юникод. Например последовательность \u1055?\u1088?\u1080?\u1074?\u1077?\u1090? выведет слово “Привет”.

В преобразовании я использовал только метод, переводящий текст в последовательности Юникода, однако для полноты картины вставил метод, переводящий строки в последовательности ANSI, который не используется.

Код преобразования:

1: <?xml version="1.0" encoding="utf-8"?>
2: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
3:     xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
4:     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5:     xmlns:custom="urn:schemas-custom">
6:     <xsl:output method="text" indent="no" encoding="us-ascii"/>
7:   <xsl:variable name="columns" select="/*/xsd:schema//xsd:element[count(ancestor::xsd:element) = 2]"/>
8:     <xsl:template match="/">
9:       <xsl:text>{\rtf1\ansi\ansicpg1251\deff0\deflang1049\deflangfe1049\deftab708
10: {\fonttbl{\f0\froman\fprq2\fcharset204{\*\fname Times New Roman;}
11: Times New Roman CYR;}{\f1\fswiss\fprq2\fcharset204 Calibri;}}
12: 
13: {\colortbl ;
14: \red255\green255\blue255;
15: \red0\green0\blue0;
16: \red0\green255\blue255;
17: \red217\green217\blue217;}
18: 
19: {\*\generator Msftedit 5.41.21.2509;}
20: \viewkind4\uc1\trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 
21: \trbrdrt\brdrs\brdrw10 \trbrdrr\brdrs\brdrw10 
22: \trbrdrb\brdrs\brdrw10 \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3
23: </xsl:text>
24:       <!--Определение ячеек заголовка-->
25: 
26:       <xsl:for-each select="$columns">
27:         <xsl:text>\clcfpat2\clcbpat1\clshdng10000\cellx</xsl:text>
28:         <xsl:value-of select="position()*2000"/>
29:         <xsl:text>&#13;&#10;</xsl:text>
30:       </xsl:for-each>
31:       
32:       <xsl:text>\pard\intbl\qc\cf1\b\f1\fs22 </xsl:text>
33:       <!--Строка заголовка-->
34:       <xsl:for-each select="$columns">
35:         <xsl:text> </xsl:text>
36:         <xsl:value-of select="custom:StringToRtfUnicode(@name)"/>
37:         <xsl:text>\cell</xsl:text>
38:       </xsl:for-each>
39:       <xsl:text>\row</xsl:text>
40:       <xsl:apply-templates select="/*/*[position() &gt; 1]"/>
41:       <xsl:text>\pard\sa200\sl276\slmult1\par}</xsl:text>
42:     </xsl:template>
43: 
44:   <xsl:template match="/*/*[position() &gt; 1]">
45:     <xsl:variable name="name" select="name()"/>
46:     <xsl:variable name="cellbgcolor">
47:       <xsl:choose>
48:         <xsl:when test="count(preceding-sibling::*[name() = $name]) mod 2 = 0">
49:           <xsl:text>\clcbpat1</xsl:text>
50:         </xsl:when>
51:         <xsl:otherwise>
52:           <xsl:text>\clcbpat4</xsl:text>
53:         </xsl:otherwise>
54:       </xsl:choose>
55:     </xsl:variable>
56:     <xsl:text>
57: \trowd\trgaph108\trleft-108\trbrdrl\brdrs\brdrw10 \trbrdrt\brdrs\brdrw10 
58: \trbrdrr\brdrs\brdrw10 \trbrdrb\brdrs\brdrw10 
59: \trpaddl108\trpaddr108\trpaddfl3\trpaddfr3
60: </xsl:text>
61:     <xsl:variable name="currentrow" select="."/>
62:     <xsl:for-each select="$columns">
63:       <xsl:value-of select="$cellbgcolor"/>
64:       <xsl:text>\clbrdrl\brdrw10\brdrs\clbrdrt\brdrw10\brdrs\clbrdrr\brdrw10\brdrs\clbrdrb\brdrw10\brdrs\cellx</xsl:text>
65:       <xsl:value-of select="position()*2000"/>
66:       <xsl:text>&#13;&#10;</xsl:text>
67:     </xsl:for-each>
68:     <xsl:text>\pard\intbl\cf0\b0 </xsl:text>
69:     <xsl:for-each select="$columns">
70:       <xsl:variable name="columnname" select="@name"/>
71:       
72:       <xsl:value-of select="custom:StringToRtfUnicode($currentrow/*[name() = $columnname])"/>
73:       <xsl:text>\cell&#13;&#10;</xsl:text>
74:     </xsl:for-each>
75:     <xsl:text>\row</xsl:text>
76:   </xsl:template>
77: 
78:   <msxsl:script language="vb" implements-prefix="custom">
79:     <msxsl:assembly name="System.Core"/>
80:     <msxsl:assembly name="Microsoft.VisualBasic"/>
81:     <msxsl:using namespace="System.Linq"/>
82:     <msxsl:using namespace="Microsoft.VisualBasic"/>
83:     <![CDATA[
84:     Function StringToRtfAnsi(ByVal input As String) As String
85:     Return String.Join( _
86:             "", _
87:             From ch As Char In input _
88:             Let chNum = Asc(ch) _
89:             Select If(chNum < 16, "\'0", "\'") _
90:             & Convert.ToString(chNum, 16))
91:     End Function
92:         
93:     Function StringToRtfUnicode(ByVal input As String) As String
94:        Return String.Join( _
95:         "", _
96:         From ch As String In input _
97:         Select "\u" & AscW(ch) & "?")
98:     End Function
99:     ]]>
100:   </msxsl:script>
101: </xsl:stylesheet>
102: 

В выходном файле будут лишние переходы строк, которые вставлены для наглядности, если размер выходных данных надо минимизировать, то их надо убрать.