Поделиться через


Windows PowerShell

Создание дружественных к пользователю XML-интерфейсов с помощью Windows PowerShell

Джо Лейбовиц

Продукты и технологии:

Windows PowerShell, Windows Forms, XML, XPath

В статье рассматриваются:

  • создание механизма разбора XML;
  • конструирование Windows Forms;
  • поиск или редактирование XML-файлов с использованием XPath-запросов.

Исходный код можно скачать по ссылке.

Скриптовый язык Windows PowerShell делает все, что можно пожелать для инструмента командной строки, и даже настолько больше, что в конечном счете он мог бы заменить такие технологии, как VBScript. Хорошее общее описание того, что такое Windows PowerShell, и основы его применения см. по ссылкам bit.ly/LE4SU6 и bit.ly/eBucBI.

Windows PowerShell полностью интегрирован с Microsoft .NET Framework, а значит, глубоко связан с XML — текущим международным стандартом для обмена данными с использованием структурированных текстовых файлов. Общую информацию о XML см. по ссылке bit.ly/JHfzw.

В этой статье исследуются возможности Windows PowerShell в представлении XML-данных и манипуляций над ними с целью создания относительно простого UI для чтения и редактирования XML-файлов. Идея в том, чтобы упростить и сделать это более удобным, используя алгоритмы, «понимающие» отношения одного уровня и предок-потомок в любом конкретном файле, даже ничего не зная о его схеме. Я также рассмотрю применение Windows Forms в Windows PowerShell, запросы XPath и другие релевантные технологии. Предлагаемое мной приложение может обрабатывать XML-файл и генерировать XPath-запросы.

Давайте обсудим, как анализировать любой XML-файл в Windows PowerShell и представить его в формате, которым смогут пользоваться люди без глубоких технических познаний. На рис. 1 показан предварительный набросок того типа GUI, который вы можете создать.

Предварительный набросок GUI
Рис. 1. Предварительный набросок GUI

Ключом к тому, чтобы добиться всего этого, является введение в приложение Windows PowerShell поддержки разбора и понимания любого XML-файла без знания его схемы или вмешательства человека. После исследования существующих технологий автоматизированного анализа XML-файлов я решил разработать свой механизм синтаксического анализа (разбора) для этой специфической цели, так как мне не удалось найти ничего, что полностью удовлетворяло бы моим требованиям (программа должна понимать XML-документы без участия человека). В настоящее время приложения, как правило, предполагают, что разработчик или пользователь хорошо знаком с элементами, атрибутами и общей схемой любого конкретного XML-документа. Но на практике в ряде ситуаций (возможно, даже во многих) это вовсе не так. Например, такая парадигма ведет к провалу в сценарии, где множество потребителей данных, не являющихся экспертами в области XML, вынуждены обращаться к самым разнообразным источникам XML-данных. Аналогично даже при наличии одного-двух экспертов, если организации приходится иметь дело с сотнями или тысячами XML-файлов с разной структурой, их обработка людьми может стать неэффективной.

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

Механизм разбора XML

Для совместимости с XML в любом документе открывающие и закрывающие скобки должны быть парными друг другу. Например, если существует элемент <ABC>, где-то дальше в том же файле должен присутствовать и элемент </ABC>. Между этими открывающей и закрывающей угловыми скобками теоретически может находиться все, что угодно. Используя этот фундаментальный принцип XML, я покажу, как автоматически конструировать серию XPath-запросов так, чтобы даже относительно неопытный потребитель XML-данных мог быстро задействовать их для поиска данных в XML-файлах и манипуляций над этими данными.

Для совместимости с XML в любом документе открывающие и закрывающие скобки должны быть парными друг другу.

Первым делом подготовим набор массивов для хранения всех открывающих и закрывающих скобок в XML-файле:

[int[]]$leading_brackets = @()
[int[]]$closing_brackets = @()
[string[]]$leading_value = @()
[string[]]$closing_value = @()

Чтобы создать в Windows PowerShell строго типизированный массив неизвестного размера, нужны три элемента: [type[]], $name и символ массива неизвестного размера — @(). Переменные в Windows PowerShell принимают $ как символ, с которого они начинаются. Конкретно эти массивы охватывают индексируемые адреса открывающих и закрывающих угловых скобок в XML-документе, а также строковые значения имен элементов, сопоставленных с этими скобками. Например, для строки <PS1>text value</PS1> в XML целочисленный индекс открывающих скобок был бы равен 0, а индекс закрывающих — 15. Открывающим и закрывающим значениями в этом случае был бы один и тот же PS1.

Чтобы получить наш целевой XML в память, используем следующий код:

$xdoc = New-Object System.Xml.XmlDocument
       $xdoc.Load("C:\temp\XMLSample.xml")

На рис. 2 дан фрагмент используемого XML-файла.

Рис. 2. Фрагмент примера XML-файла

<?xml version="1.0" encoding="utf-8"?>
<Sciences>
  <Chemistry>
    <Organic ID="C1" origination="Ancient Greece" age="2800 yrs">
      <branch ID="C1a">
        <size>300</size>
        <price>1000</price>
        <degree>easy&gt;</degree>
        <origin>Athens</origin>
        // Здесь текст по органической химии
      </branch>
      <branch name="early" ID="C1b" source="Egypt" number="14">
        <size>100</size>
        <price>3000</price>
        <degree>hard&gt;</degree>
        <origin>Alexandria</origin>
        // Здесь текст по наукам в Древнем Египте
      </branch>
    </Organic>
  </Chemistry>
<Physics></Physics>
<Biology ID="B" origination="17th century" >
.
.
.
      <Trees4a name="trees4a" age="40000000">
        <type ID="Tda1">oakda</type>
        <type ID="Tda2">elmda</type>
        <type ID="Tda3">oakd3a</type>
      </Trees4a>
    </Plants>
  </Biology>
</Sciences>

После операции загрузки эти XML-данные находятся в памяти. Чтобы проанализировать этот XML и манипулировать им, я использую объектную модель документа (document object model, DOM), экземпляр которой теперь создается в переменной $xdoc (но мне также понадобится задействовать технологию XPathNavigator для некоторых специфических целей, о которых я расскажу немного позже):

# Создаем XPath-навигатор (комментарии в PowerShell-коде
# начинаются с символа \"#\")
$nav = $xdoc.CreateNavigator()

Одно из наиболее интересных средств Windows PowerShell — встроенная функция, или командлет (cmdlet), называемый Get-Member; он позволяет просматривать методы и свойства любого объекта в Windows PowerShell прямо в IDE в процессе разработки. На рис. 3 показаны результаты вызова этого командлета применительно к только что созданному объекту $nav, а на рис. 4 — результаты, отображаемые в Windows PowerShell Integrated Scripting Environment (ISE) после вызова Get-Help.

Рис. 3. Результаты вызова Get-Member

Get-Member -InputObject $nav
                      TypeName: System.Xml.DocumentXPathNavigator

Name                 MemberType Definition
----                 ---------- ----------
AppendChild          Method     System.Xml.XmlWriter AppendChild(), System.V...
AppendChildElement   Method     System.Void AppendChildElement(string prefix...
CheckValidity        Method     bool CheckValidity(System.Xml.Schema.XmlSche...
Clone                Method     System.Xml.XPath.XPathNavigator Clone()
ComparePosition      Method     System.Xml.XmlNodeOrder ComparePosition(Syst...
Compile              Method     System.Xml.XPath.XPathExpression Compile(str...
CreateAttribute      Method     System.Void CreateAttribute(string prefix, s...
CreateAttributes     Method     System.Xml.XmlWriter CreateAttributes()
CreateNavigator      Method     System.Xml.XPath.XPathNavigator CreateNaviga...
DeleteRange          Method     System.Void DeleteRange(System.Xml.XPath.XPa...
DeleteSelf           Method     System.Void DeleteSelf()
Equals               Method     bool Equals(System.Object obj)
Evaluate             Method     System.Object Evaluate(string xpath), System...
GetAttribute         Method     string GetAttribute(string localName, string...
GetHashCode          Method     int GetHashCode()
TypeName: System.Xml.DocumentXPathNavigator

.
.
.
.
.
Value                Property   System.String Value {get;}
ValueAsBoolean       Property   System.Boolean ValueAsBoolean {get;}
ValueAsDateTime      Property   System.DateTime ValueAsDateTime {get;}
ValueAsDouble        Property   System.Double ValueAsDouble {get;}
ValueAsInt           Property   System.Int32 ValueAsInt {get;}
ValueAsLong          Property   System.Int64 ValueAsLong {get;}
ValueType            Property   System.Type ValueType {get;}
XmlLang              Property   System.String XmlLang {get;}
XmlType              Property   System.Xml.Schema.XmlSchemaType XmlType {get;}

Результаты вызова Get-Help в Windows PowerShell
Рис. 4. Результаты вызова Get-Help в Windows PowerShell

While Get-Member will often put you on the right track during Windows PowerShell development, you'll also find the associated Get-Help cmdlet handy during this process.

If you type get-help xml at the command line, as shown in Figure 4, you'll get the output shown here:

getName                 Category  Synopsis
----                 --------  --------
Export-Clixml        Cmdlet    Creates an XML-based representation of an object or...
Import-Clixml        Cmdlet    Imports a CLIXML file and creates corresponding obj...
ConvertTo-XML        Cmdlet    Creates an XML-based representation of an object.
Select-XML           Cmdlet    Finds text in an XML string or document.
about_format.ps1xml  HelpFile  The Format.ps1xml files in Windows PowerShell defin...
about_types.ps1xml   HelpFile  Explains how the Types.ps1xml files let you extend ...

Если вы введете get-help about_types.ps1xml, то получите результаты, как на рис. 5..

Рис. 5. Получение справки о файлах Types.ps1xml

TOPIC
    about_Types.ps1xml
SHORT DESCRIPTION
    Explains how the Types.ps1xml files let you extend the Microsoft .NET Framework types of the objects that are used in Windows PowerShell.
LONG DESCRIPTION
    The Types.ps1xml file in the Windows PowerShell installation directory ($pshome) is an XML-based text file that lets you add properties and methods to the objects that are used in Windows PowerShell. Windows PowerShell has a built-in Types.ps1xml file that adds several elements to the .NET Framework types, but you can create additional Types.ps1xml files to further extend the types.
SEE ALSO
    about_Signing
    Copy-Item
    Get-Member
    Update-TypeData

Интегрированная система Windows PowerShell обеспечивает всестороннее исследование синтаксиса и сравнительно проста в использовании. Эта тема вообще заслуживает отдельной статьи.

Чтобы привести XML к состоянию, готовому для анализа, используйте метод Select объекта XpathNavigator:

$nav.Select("/") | % { $ouxml = $_.OuterXml }

В первой части этого выражения я вызываю .Select в простом XPath-запросе «/», предоставляя весь XML-контент. Во второй части, после символа «|», обозначающего в Windows PowerShell объектный конвейер, я выполняю foreach, представленный псевдонимом %; вместо этого псевдонима я мог бы использовать foreach. В цикле я формирую строковые XML-данные в переменной $ouxml из свойства .OuterXML для объектов, обрабатываемых в этом цикле. Вернувшись к рис. 3, вы увидите, что .OuterXML является одним из свойств объекта XPathNavigator. Это свойство предоставляет полный набор всех угловых скобок в XML-файле, необходимых для корректной работы механизма разбора.

Заметьте, что для объектов, проходящих конвейер, $_ является символом конкретного экземпляра, а нотация с точкой используется для получения свойств и методов каждого экземпляра. Адресация или ссылка на каждый объект в конвейере осуществляется с помощью символа $_.  Чтобы получить атрибут объекта $_, напишите, например, $_.Name (еслиName — свойство-член конкретного объекта). Все, что передается через конвейер Windows PowerShell, является объектом со свойствами и методами.

Последняя фаза подготовки к разбору — «регуляризация» («regularize») XML-текста с обработкой любых особых случаев, которые выглядят как <ShortNode/>. Механизм разбора увидит ту же информацию в другом формате: <ShortNode></ShortNode>. Следующий код начинает это преобразование, используя RegEx и отыскивая совпадения:

$ms = $ouxml | select-string –pattern"<([a-zA-Z0-9]*)\b[^>]*/>"   -allmatches
foreach($m in $ms.Matches){ 'regularize' to the longer format }

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

Рис. 6. Проверка файла на наличие открывающих скобок

# Если у вас закончились подготовленные вами \"<\", используйте
# булеву переменную $found_bracket для проверки на наличие \"<\"
$found_bracket = $true
while($found_bracket -eq $true)
{
  # Особый случай первого, или корневого, элемента
  # XML-документа; здесь переменная $ctr уровня скрипта равна 0
    if($Script:ctr -eq 0)
    {
    # для обработки корня верхнего уровня
    $leading_brackets += $ouxml.IndexOf(
      "<",$leading_brackets[$Script:ctr])
    $leading_value += $ouxml.Substring(0,$ind)
    $closing_brackets += $ouxml.IndexOf("</" +
      $leading_value[0].Substring(1))
    $Script:ctr+=1
    }
}

Код на рис. 6 обрабатывает особый случай с корневым элементом XML-документа. Еще одно фундаментальное правило XML заключается в том, что каждая схема должна содержать один общий корневой набор (overall root set) угловых скобок; внутри этих замыкающих символов XML-данные можно структурировать как угодно, лишь бы это отвечало ранее упомянутому правилу парности, т. е. для каждого <ABC> должен быть </ABC.

Заметьте, что для добавления элемента в массив используется синтаксис +=. Впоследствии после заполнения элементами к такому массиву можно будет обращаться по индексам, как в $leading_brackets[3].

Заметьте, что в аргументах IndexOf начальная позиция для поиска, представляемая вторым параметром в метода, — ссылка на $Script:ctr. В Windows PowerShell переменные имеют разные области видимости, зависящие от того, где создаются переменные. Так как здесь переменная $ctr создается вне любой функции, она считается переменной уровня скрипта, а такую переменную нельзя изменять в какой-либо функции без ссылки на $Script. Внутри цикла ссылка $Script скорее всего не потребуется, но хорошим стилем считается, когда область действия указывается в любом случае.

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

После обработки корневого элемента все остальные элементы обрабатываются в одном блоке else:

else
{
# Проверяем, есть ли еще \"<\"
$check = $ouxml.IndexOf("<",$leading_brackets[$Script:ctr-1]+1)
if($check -eq - 1)
{
break
}

Первым делом проверяйте, не достигнут ли конец файла; критерий этого события — отсутствие очередного символа <. В предыдущем фрагменте кода как раз это и делается. Если символов < больше нет, вызывается break.

Следующая часть кода обрабатывает случаи < и </:

#eliminate "</" cases of "<"
if($ouxml.IndexOf("</",$leading_brackets[$Script:ctr-1]+1) -ne`
  $ouxml.IndexOf("<",$leading_brackets[$Script:ctr-1]+1))

Обратите внимание на синтаксис Windows PowerShell для «не равно» в сравнениях: –ne. Другие операторы сравнения включают –eq, –lt и –gt.

Поскольку вы пытаетесь аккумулировать все открывающие угловые скобки, на этом этапе операций разбора вам требуется знать только о них. Обратите внимание на синтаксис Windows PowerShell для «не равно» в сравнениях: –ne. Другие операторы сравнения включают –eq, –lt и –gt. Кроме того, как и в Visual Basic (но не как в C#), нужно использовать символ продолжения строки кода при ее переносе — в Windows PowerShell это символ `.

Если проверка выполнена успешно, заполняем массив $leading_brackets новым элементом:

$leading_brackets += $ouxml.IndexOf("<",$leading_brackets[$Script:ctr-1]+1)

Следующая задача — изолировать имя сопоставленного элемента. Заметьте, что после открывающей угловой скобки < и имени элемента, <ElementName, присутствует либо пробел, за которым следует один или более атрибутов, либо закрывающая угловая скобка, как в этих двух случаях:

<ElementName attribute1="X" attribute2 = "Y">
или
<ElementName>

Эти два случая разделяются следующим кодом, который проверяет, что появляется первым — пробел или символ >:

$indx_space = $ouxml.IndexOf(" ",
  $leading_brackets[$Script:ctr])
  $indx_close = $ouxml.IndexOf(">",
  $leading_brackets[$Script:ctr])
  if($indx_space -lt $indx_close)
  {
  $indx_to_use = $indx_space
  }
  else
  {
  $indx_to_use = $indx_close
  }

Как только вы нашли конечную точку, используйте $indx_to_use, чтобы было проще выделить строку, сопоставленную с открывающей угловой скобкой, которая сейчас находится в фокусе:

$leading_value += $ouxml.Substring($leading_brackets[`
  $Script:ctr],($indx_to_use - $leading_brackets[$Script:ctr]))

По сути, открывающее значение (leading value) — это строка, начинающаяся с < и заканчивающаяся либо пробелом, либо >.

Теперь можно извлечь парные закрывающие угловые скобки поиском строки </ElementName:

$closing_brackets += $ouxml.IndexOf("</" + $leading_value[$Script:ctr].Substring(1),`
  $leading_brackets[$Script:ctr]+1)
$Script:ctr+=1

Наконец, если различия между < и </ не найдено, увеличивайте элемент массива и продолжайте:

else
{
$leading_brackets[$Script:ctr-1] +=1
}

В конце этого процесса три массива будут выглядеть следующим образом (показан лишь фрагмент):

$leading_brackets:
0 18 62 109 179 207 241 360 375 447 475 509 625 639 681 713 741
775 808 844 900 915 948 976 1012 1044 1077 1142 1154 1203 1292
1329 1344 1426 1475 1490 1616 1687 1701 1743 1810 1842 1890
1904 1941 1979 2031 2046 2085 2138 2153 2186 2235 2250 2315
2362 2378 2442 2476 2524 2539 2607 2643 2718
$leading_value:
<Sciences <Chemistry <Organic <branch <size <price <degree
<origin <branch <size <price <degree <origin <Physics <Biology
$closing_brackets:
2718 1687 625 360 179 207 241 273 612 447 475 509 541 1142 900
713 741 775 808 844 882 1129 948 976 1012 1044 1077 1

Установление взаимосвязей между узлами

Теперь пора заняться второй фазой операций разбора. На этом более сложном этапе последовательности $leading_brackets и $closing_brackets устанавливают отношения «предок-потомок» и одноуровневые отношения между всеми узлами в разбираемом XML. Сначала определяется ряд переменных:

# Эти переменные будут использоваться при формировании
# автоматически предоставляемого списка XPath-запросов
$xpaths = @()
$xpaths_sorted = @()
$xpath = @()
[string]$xpath2 = $null

Затем фиксируется первая пара смежных открывающих и закрывающих угловых скобок:

$first_open = $leading_brackets[0]
$first_closed = $closing_brackets[0]
$second_open = $leading_brackets[1]
$second_closed = $closing_brackets[1]

И создаются счетчики цикла:

$loop_ctr = 1
$loop_ctr3 = 0

Механизм будет выполнять итеративный разбор столько раз, сколько указывает значение переменной $ctr, увеличивавшееся в первой фазе при формировании $leading_brackets и других массивов (следующее выражение if является «тестом на лакмусовую реакцию» в смысле установления структуры узлов XML):

if($second_closed -lt $first_closed)

Если значение $second_closed меньше (–lt) значения $first_closed, устанавливается дочернее отношение:

<ElementOneName>текст для этого элемента
  <ChildElementName>закрывается до закрытия его предка
  </ChildElementName>
</ElementOneName>

После обнаружения дочернего узла переменные переустанавливаются на следующие две смежные пары открывающих и закрывающих угловых скобок, счетчики увеличиваются, а массив $xpath заполняется новым элементом:

$first_open = $leading_brackets[$loop_ctr]
$first_closed = $closing_brackets[$loop_ctr]
$second_open = $leading_brackets[$loop_ctr + 1]
$second_closed = $closing_brackets[$loop_ctr + 1]
$loop_ctr2 +=1
#if($loop_ctr2 -gt $depth){$loop_ctr2 -= 1}
$depth_trial+=1
$xpath += '/' + $leading_value[$loop_ctr-1]
$loop_ctr+=1

Теперь вы достигли критически важного этапа обработки в механизме разбора: что делать, когда отношения «предок-потомок» больше нет.

Располагая всеми автоматически формируемыми XPath-запросами, вы готовы к созданию приложения Windows Forms для пользователей.

Предварительно следует исключить дубликаты, которые появятся в процессе разбора. Для этого переменная, хранящая весь массив XPath-запросов (это ключевая переменная, конструируемая механизмом разбора), последовательно просматривается на предмет того, не содержит ли она тот же элемент, что и новый кандидат на включение в $xpaths, который в этот момент является текущим значением $xpath, устанавливаемым в восьмой строке кода на рис. 7.

Рис. 7. Проверка на дубликаты в Xpaths

$is_dupe = $false
  foreach($xp in $xpaths)
  {
  $depth = $xpath.Length
  $xp = $xp.Replace('/////','')
  $xpath2 = $xpath
  $xpath2 = $xpath2.Replace(" ","")
  $xpath2 = $xpath2.Replace("<","")
  if($xp -eq $xpath2)
  {
  $is_dupe = $true
  #write-host 'DUPE!!!'
  break
}

Если текущее значение $xpath не является дубликатом, оно добавляется в массив $xpaths, а $xpath создается заново как пустой массив для использования в дальнейшем:

iif($is_dupe -eq $false){$xpaths += ($xpath2 + '/////');}
$xpath = @()
$xpath2 = $null

Важнейший прием, используемый механизмом разбора для итеративного прохода по XML, — повторное создание массивов на каждой итерации. Для этого первым делом создается новый промежуточный массив объектов:

$replacement_array_values = @()
$replacement_array_opens = @()
$replacement_array_closes = @()
$finished = $false
$item_ct = 0

Механизм перебирает массив $leading_value в цикле и отфильтровывает только текущий элемент:

foreach($item in $leading_value)
{
if($item -eq $leading_value[$loop_ctr - 1] -and `
  $finished -eq $false)
{
$finished = $true
$item_ct+=1
continue  # этот элемент должен быть отфильтрован
}

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

$replacement_array_values += $item
$replacement_array_opens += $leading_brackets[$item_ct]
$replacement_array_closes += $closing_brackets[$item_ct]
$item_ct +=1

Когда три промежуточные массива заполнены, трем постоянным массивам присваиваются их новые значения:

$leading_value = $replacement_array_values
  $opening_brackets = $replacement_array_opens
  $closing_brackets = $replacement_array_closes
  $loop_ctr+=1

Стоит отметить, что add_Click — это способ подключения события к элементу управления в Windows PowerShell.

Следующая итерация первой фазы механизма разбора готовится инициализацией первых смежных пар угловых скобок:

$first_open = $leading_brackets[0]
$first_closed = $closing_brackets[0]
$second_open = $leading_brackets[1]
$second_closed = $closing_brackets[1]
$loop_ctr = 1
$loop_ctr2 = 1
continue  # возвращает обратно в начало цикла

Наконец, чтобы создать набор XPath-запросов, вы генерируете короткие пути, которые в ранее описанном процессе были опущены. Так, в текущем примере без этого дополнительного шага XPath не включила бы \Sciences\Chemistry. Нижележащая логика заключается в проверке того, что для каждого XPath-запроса также существует своя более короткая версия — без дубликатов. Функция, которая выполняет эту операцию, — AddMissingShortPaths, и вы можете увидеть ее пакете исходного кода для этой статьи.

Располагая всеми автоматически формируемыми XPath-запросами, вы готовы к созданию приложения Windows Forms для пользователей. Тем временем только что сгенерированные XPath-запросы помещаются в файл C:\PowerShell\XPATHS.txt с использованием синтаксиса вывода >> в Windows PowerShell.

Создание приложения Windows Forms

Поскольку Windows PowerShell является хостом для .NET-библиотек и классов, вы можете писать следующий код и тем самым сделать доступными для своего приложения Windows Forms и .NET-классы Drawing:

[void] [Reflection.Assembly]::LoadWithPartialName(
  "System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName(
  "System.Drawing")

Теперь вы можете создать форму и разместить на ней элементы управления:

$form= New-Object Windows.Forms.Form
$form.Height = 1000
$form.Width = 1500
$drawinfo = 'System.Drawing'
$button_get_data = New-Object Windows.Forms.button
$button_get_data.Enabled = $false
$text_box = New-Object Windows.Forms.Textbox
$button_get_data.Text = "get data"
$button_get_data.add_Click({ShowDataFromXMLXPathFilter})

Стоит отметить, что add_Click — это способ подключения события к элементу управления в Windows PowerShell. В данном случае к событию щелчка кнопки подключается вызов функции. Код на рис. 8 добавляет кнопки и текстовые поля.

Рис. 8. Добавление кнопок и текстовых полей

$$pointA = New-Object System.Drawing.Point
$listbox = New-Object Windows.Forms.Listbox
$form.Controls.Add($listbox)
$listbox.add_SelectedIndexChanged({PopulateTextBox})
$form.Controls.Add($button_get_data)
$form.Controls.Add($text_box)
$pointA.X = 800
$pointA.Y = 100
$button_get_data.Location = $pointA
$button_get_data.Width = 100
$button_get_data.Height = 50
$pointA.X = 400
$pointA.Y = 50
$text_box.Location = $pointA
$text_box.Width = 800

Чтобы заполнить $listbox вашим набором XPath-запросов, сделайте так:

foreach($item in $xpaths)
{
$listbox.Items.Add($item.Substring(0,$item.Length - 5))
# Каждая строка в listbox должна быть отделена пустой строкой
$listbox.Items.Add('     ')
}

UI

На рис. 9 показан UI со сгенерированными XPath-запросами; при этом один из запросов выбран пользователем.
Выбор XPath-запроса
Рис. 9. Выбор XPath-запроса

На финальном этапе пользователь нажимает кнопку GetXMLData и получает результаты, приведенные на рис. 10.

Окно результатов
Рис. 10. Окно результатов

Вот и все — вы получили простой UI для чтения и редактирования XML-файлов, созданный исключительно средствами Windows PowerShell. В последующих онлайновых статьях я продолжу эту тему и расскажу, как обрабатывать XML-файлы, использующие пространства имен, а также продемонстрирую применение показанных здесь методик для поддержки редактирования XML-файлов через этот интерфейс.


Джо Лейбовиц (Joe Leibowitz) — консультант, специализирующийся на инфраструктурных проектах. С ним можно связаться по адресу joe.leibowitz@bridgewaresolutions.com.

Выражаю благодарность за рецензирование статьи эксперту Томасу Петчелу (Thomas Petchel).