Мастер формирования Web-форм на основе HTML-страниц

Мастер формирования Web-форм на основе HTML-страниц

Приводится пример построения wizard на основе встроенного в VS.Net Wizard Engine
Сергей Иванов

Современные системы программирования в значительной степени оцениваются с точки зрения удобства использования среды разработки (IDE - integrated development environment). При этом существенными являются такие характеристики IDE, как оснащенность ее различными мастерами (wizards) и возможность расширения функциональности среды разработки.

Необходимо признать, что по обоим параметрам Microsoft Visual Studio.Net может быть оценена как весьма качественно реализованная среда разработки. Встроенные в нее мастера позволяют автоматизировать или, по крайней мере, значительно упростить выполнение широчайшего спектра рутинных операций разработки, начиная от генерации Windows- и Web-форм и заканчивая добавлением к классам полей, методов и свойств. Возможности VS.Net в части расширения также впечатляют – здесь и макросы, и модули расширения (add-ins), и даже возможность создания своих собственных мастеров. Именно последней возможности, т.е. созданию мастеров для VS.Net, и посвящена настоящая статья.

В общем случае в качестве мастера для Microsoft Visual Studio.Net может быть использован любой COM-объект, реализующий интерфейс IDTWizard. Указанный интерфейс имеет всего один метод Execute, который вызывается при запуске мастера и должен обеспечивать всю работу мастера, включая вывод окон, навигацию между ними, а также взаимодействие со средой разработки. Электронная документация, поставляемая с VS.Net, содержит достаточно подробное описание интерфейса IDTWizard и его метода Execute. Для доступа к контексту среды Visual Studio.Net в ходе работы мастера служит объектная модель VS.Net. При этом ссылка на работающий экземпляр VS.Net передается через параметр Application метода Execute, а состояние IDE – через параметр ContextParams.

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

С точки зрения разработчика создание мастера для VS.Net мало чем отличается от создания обычного Windows-приложения. И хотя для программирования мастера можно использовать не только C++, но и VC# или VB.Net (а в общем случае и любой другой инструментарий, способный строить COM-объекты, например, Delphi), тем не менее трудоемкость этого процесса оказывается достаточно высокой.

К счастью, Microsoft Visual Studio.Net уже содержит довольно гибкий и в то же время мощный «движок» для реализации мастеров – именно на его основе работает большинство встроенных мастеров VS.Net. Этот «движок» (Wizard Engine) выполнен в виде Net-библиотеки EnvDTE и реализует специальную объектную модель Visual C++ Wizard Model. Забегая несколько вперед, можно отметить, что наиболее «используемым» при разработке своего мастера является объект VCWizCtl.

Несмотря на то, что речь в документации на Wizard Engine идет только в контексте Visual C++ и что только в списке шаблонов C++-проектов имеется возможность создать свой мастер (File – New – Project… - Visual C++ Projects – Custom Wizard), использовать Wizard Engine можно для построения мастеров произвольного назначения.

Рассмотрим эту возможность более подробно на примере построения мастера преобразования HTML-страниц в Web-формы. Разрабатываемый мастер должен будет генерировать форму ASP.Net, максимально соответствующую указанной в качестве основы HTML-странице, и подключать сгенерированную форму к текущему Web-проекту (язык реализации – C#).

Для того, чтобы не загромождать результирующий код, используем простейший алгоритм преобразования:

  • прочитать указанную HTML-страницу в память и выполнить необходимые проверки на возможность преобразования страницы в Web-форму;
  • если загруженная страница отвечает требованиям преобразователя, добавить в нее необходимую информацию (тэг Asp.Net <%@ Page>, метаинформацию заголовка, HTML-тэг <form>);
  • записать результат в виде файла;
  • добавить файл к текущему проекту (на верхнем уровне).

Единственное ограничение на исходные HTML-страницы – страница не должна содержать фреймов (тэг <FRAMESET>).

Разрешим себе еще пару упрощений:

  • если исходная страница содержит формы, то только первая из них будет перенастраиваться для типовой работы Web-формы; исходное описание первой формы будем сохранять в виде комментария, предшествующего первому тэгу <FORM>, остальные формы, если есть, будем записывать без изменений;
  • если исходная страница содержит скрипты, ссылки на элементы управления в них корректировать не будем.

Дальнейший план действий прост:

  • сгенерировать в качестве основы мастер C++ с использованием шаблона C++-проекта;
  • внести необходимые корректировки в сгенерированный мастер с тем, чтобы он смог работать для C#;
  • запрограммировать логику преобразования исходной HTML-страницы в Web-форму.

Приступим к реализации намеченного плана.

Генерация мастера C++ с использованием шаблона C++-проекта выполняется с помощью соответствующего мастера, встроенного в Microsoft Visual Studio.Net (File – New – Project… - Visual C++ Projects – Custom Wizard).

После подтверждения имени проекта запускается в работу мастер VS.Net, где требуется настроить некоторые параметры генерируемого мастера: укажем, что мы хотим сгенерировать мастер с (визуальным) пользовательским интерфейсом, состоящим из одного окна. После нажатия на кнопку Finish в папке проекта будут сгенерированы следующие каталоги и файлы:

	1033
	└ styles.css
	HTML
	└ 1033
	  └ default.htm
	Images
	└ Html2Aspx.gif
	└ Html2Aspx_Background.gif
	Scripts
	└ 1033
	  └ default.js
	Templates
	└ 1033
	  └ ReadMe.txt
	  └ Sample.txt
	  └ Templates.inf
			   

Относительно серьезной доработке достаточно подвергнуть только выделенные файлы.

Файл default.htm представляет собой HTML-страницу, определяющую внешний вид пользовательского интерфейса нашего мастера и некоторую минимальную функциональность, которую можно реализовать в контексте этой страницы. Внешний вид страницы скорректируем в дизайнере HTML-страниц VS.Net, убрав ненужные элементы управления и добавив элемент управления File Field из палитры Toolbox – HTML Установим также на страницу невидимое поле ввода (оно нам понадобится позже для хранения имени файла, выбранного пользователем в качестве исходной HTML-страницы).

Корректировка функциональности HTML-страницы будет включать несколько шагов (слева – исходный вариант, справа – конечный):

  1. Изменим ссылку на стилевые таблицы, используемые в странице

    strURL += "VCWizards/"; strURL += window.external.GetHostLocale();

  2. Удалим ненужные ссылки на элементы таблицы символов

    <SYMBOL NAME='WIZARD_DIALOG_TITLE' TYPE=text VALUE='Html2Aspx'></SYMBOL>
    <SYMBOL NAME='SAMPLE_CHECKBOX' TYPE=checkbox VALUE=true></SYMBOL>
    <SYMBOL NAME='SAMPLE_RADIO_OPTION1' TYPE=checkbox VALUE=true></SYMBOL>
    <SYMBOL NAME='SAMPLE_RADIO_OPTION2' TYPE=checkbox VALUE=false></SYMBOL>
    <SYMBOL NAME='SAMPLE_LISTBOX' TYPE=select-one VALUE='option1'></SYMBOL>
    <SYMBOL NAME='SOURCE_FILTER' TYPE=text VALUE='txt'></SYMBOL>
    <SYMBOL NAME='APP_TYPE_SUMMARY' TYPE=text VALUE='//TODO: Application summary'></SYMBOL>

  3. Добавим ссылку на элемент таблицы символов, соответствующий скрытому полю ввода

    <SYMBOL NAME='SOURCE_FILE' TYPE="hidden" VALUE=''></SYMBOL>

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

    <BODY ... ONLOAD="InitDocument(document);" ...> <BODY ...>

  5. ... и удалив функцию InitDocument

    function InitDocument(document) { ... }

  6. Так как наш мастер содержит только одно окно, навигация между окнами для него не требуется, поэтому удалим функцию Next

    function Next(document, linkto) { ... }

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

    function OnFinish(document)
    {
      OnWizFinish(document);
    }
    function OnFinish(document)
    {
      // проверить имя исходного файла
      var strSourceFileName = FSOURCE_FILE.value;
      if (FSOURCE_FILE.value.length == 0)
      {
        alert("Имя файла обязательно");
        return;
      }
      // запомнить имя исходного файла для дальнейшей обработки
      SOURCE_FILE.value = FSOURCE_FILE.value;
      // вызов подпрограммы завершения работы мастера
      OnWizFinish(document);
    }

  8. Изменим ссылку на вспомогательные скрипты, используемые в странице

    strPath += "VCWizards/"; strPath += "VC#Wizards/";

В итоге должна получиться HTML-страница, ключевые моменты в которой будут выглядеть примерно так:

<HTML>
  <HEAD>
    <SCRIPT>
      var strURL = window.external.FindSymbol("PRODUCT_INSTALLATION_DIR");
      strURL += window.external.GetHostLocale();
      strURL += "/NewStyles.css";
      document.styleSheets(0).imports(0).href = strURL;
    </SCRIPT>
    <SYMBOL NAME='SOURCE_FILE' TYPE="hidden" VALUE=''></SYMBOL>
  </HEAD>
  <BODY ONKEYDOWN="OnKey();" ONKEYPRESS="OnPress();">
    <INPUT id="FSOURCE_FILE" type="file" name="FSOURCE_FILE"></INPUT>
    <INPUT id="SOURCE_FILE" type="hidden" name="SOURCE_FILE"></INPUT>
    <BUTTON ID="FinishBtn" onClick="OnFinish(document);" type="button">Завершить</BUTTON>
    <SCRIPT LANGUAGE="JSCRIPT">
      function OnFinish(document)
      {
        // проверить имя исходного файла
        var strSourceFileName = FSOURCE_FILE.value;
        if (FSOURCE_FILE.value.length == 0)
        {
          alert("Имя файла обязательно");
          return;
        }
        // запомнить имя исходного файла для дальнейшей обработки
        SOURCE_FILE.value = FSOURCE_FILE.value;
        // вызов подпрограммы завершения работы мастера
        OnWizFinish(document);
      }
    </SCRIPT>
    <SCRIPT ID="INCLUDE_SCRIPT" LANGUAGE="JSCRIPT"></SCRIPT>
    <SCRIPT ID="INCLUDE_COMMON" LANGUAGE="JSCRIPT"></SCRIPT>
    <SCRIPT>
      var strPath = window.external.FindSymbol("PRODUCT_INSTALLATION_DIR");
      strPath += "VC#Wizards/";
      strPath += window.external.GetHostLocale();
      var strScriptPath = strPath + "/Script.js";
      var strCommonPath = strPath + "/Common.js";
      document.scripts("INCLUDE_SCRIPT").src = strScriptPath;
      document.scripts("INCLUDE_COMMON").src = strCommonPath;
    </SCRIPT>
  </BODY>
</HTML>

Теперь можно реализовать основную логику работы нашего мастера в файле default.js.

По окончании работы мастера вызывается функция OnFinish, которая и выполняет основную работу. Так как исходный текст этой функции достаточно прост и к тому же снабжен комментариями, подробно рассматривать ее работу не имеет смысла. Ограничимся несколькими замечаниями:

  • в работе функции активно используется объект VCWizCtl, доступ к которому осуществляется через переменную wizard;
  • корректность формата исходного HTML-страницы проверяется «вручную» за счет поиска и определения взаимного размещения тэгов;
  • для поиска подстрок, соответствующих HTML-тэгам могли бы использоваться регулярные выражения, но при этом остается необходимость перепроверки положения тэгов внутри комментариев, поэтому поиск тэгов выполняется с помощью специальных функций GetTagIndex и StrIndexOfCaseInsensitive;
  • с целью упрощения используется генерация промежуточного файла в каталоге шаблонов – функция AddDesignerFileToCSharpWebProject выполняет конечную генерацию именно на основе файла-шаблона.

Для окончательного подключения созданного мастера остается только сформировать VSZ- и VSDIR- файлы и записать их в соответствующие каталоги.

Вот и все – наш мастер готов.

Очевидно, что описанная в настоящей статье методика построения мастеров на основе Wizard Engine не является единственно возможной. Создание прототипа мастера с помощью генератора Visual C++ Projects – Custom Wizard вовсе необязательно – можно вручную создать соответствующие HTML-страницы и файл обработки. Достаточно лишь соблюсти несколько простых правил:

  • в каталоге размещения разрабатываемого мастера необходимо создать два каталога: HTML и Scripts;
  • в обоих каталогах нужно создать подкаталоги, соответствующие локализованным версиям разрабатываемого мастера (в соответствии с правилами формирования языково-зависимых подкаталогов подкаталог 1033 должен быть обязательно);
  • в каталоге HTML\1033 (и соответствующих языково-зависимых подкаталогах) должен быть создан файл default.htm, который выполняет роль первой страницы интерфейса;
  • в каталоге Scripts\1033 (и соответствующих языково-зависимых подкаталогах) должен быть создан файл default.js, который содержит код обработки;
  • в секции <HEAD> файла default.htm можно разместить скрипт, выполняемый в процессе загрузки страницы; во всех скриптах HTML-страниц можно обращаться к объекту VCWizCtl объектной модели Wizard Model через конструкцию window.external;
  • там же (в секции <HEAD> файла default.htm) можно использовать тэги <SYMBOL>, которые обеспечивают автоматическое проецирование соответствующих элементов управления HTML-страницы на переменные, доступные в Wizard Model;
  • к HTML-странице нужно подключить внешний скрипт Common.js, содержащий дополнительные JavaScript-функции для взаимодействия с Wizard Model;
  • к элементу управления, инициирующему завершение работы мастера (например, к кнопке), нужно подключить обработчик, в контексте которого должна быть вызвана подпрограмма завершения работы мастера OnWizFinish;
  • файл default.js должен содержать функцию OnFinish, выполняющую основные действия по завершении работы мастера; доступ к объекту VCWizCtl в контексте скрипта должен осуществляться через переменную wizard;
  • в случае взаимодействия с текущим проектом VS.Net может потребоваться реализация функции SetFileProperties в файле default.js или внешнем скрипте.

Формирование и подключение VSZ- и VSDIR- файлов осуществляется аналогично «полуавтоматизированному» способу с использованием генератора Visual C++ Projects – Custom Wizard.

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