Эй, программист!XML и охота на автомобиль

Сотрудники группы Microsoft Scripting Guys

Загрузить исходный код для этой статьи: HeyScriptingGuy2007_02.exe (150KB)

«Я НЕ ЗНАЮ, зачем мой пес гоняется за автомобилями, — говорится в старом анекдоте. — Посудите сами, ну если он один из них поймает, что он делать-то с ним будет?»

Отличный вопрос. Ни один из Scripting Guys ответить на него не может. Ну разве что это про Люси, ту собаку в конце улицы... Мы совершенно уверены, что если Люси отловит автомобиль, она устроит ограбление в винном магазине, а потом на пойманной машине скроется с места преступления. И судя по тому, что она сотворила с газоном перед домом, мы готовы биться об заклад, она не скоро остановится передохнуть. Если вы, конечно, понимаете, о чем речь.

Попроси нас кто-нибудь адаптировать этот бородатый анекдотец к условиям современной эпохи высоких технологий, получилось бы что-то вроде этого: «Я понятия не имею, зачем мой сисадмин пытается выучить язык XML. Посудите сами, ну если даже она его выучит, что делать-то с ним будет?»

Заметим в свое оправдание: мы всего лишь пытались перекроить старую шутку на новый лад, мы не обещали сделать ее смешной.

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

Но также верно и то, что в приложениях все чаще используется стандарт XML для хранения данных. Собственно, почему бы и нет? Ведь файлы данных XML — это не что иное как разукрашенные текстовые файлы. Их легко создавать, их можно переносить с одной платформы на другую, и они не требуют сложных программ для работы с базами данных (не говоря уже о том, что никаких программ не надо покупать). Есть операционная система и текстовый редактор? Можете создавать базу данных XML.

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

Вопрос, конечно, справедливый, но если бы нам и впрямь его задали, мы готовы были бы поспорить, что Люси слишком увлечена раскапыванием чьей-то клумбы, чтобы писать статьи про XML.

Для начала взглянем на простенький файл XML (см. рис. 1), файл базы данных с четырьмя сценариями, которые можно найти в ежедневно обновляемой колонке Hey, Scripting Guy! (на английском языке).

Figure 1 Эй, программист! Сценарии в файле XML

<?xml version="1.0" encoding="ISO-8859-1"?>
<Repository>
  <Script>
    <Category>Microsoft Office</Category>
    <Subcategory>Microsoft Access</Subcategory>
    <Keyword>databases</Keyword>
    <Title>How Can I Print a Microsoft Access Report?</Title>
    <URL>https://www.microsoft.com/technet/scriptcenter/resources/qanda/oct06/hey1020.mspx</URL>
  </Script>
  <Script>
    <Category>Microsoft Office</Category>
    <Subcategory>Microsoft Access</Subcategory>
    <Keyword>databases</Keyword>
    <Title>How Can I Compact a Microsoft Access Database?</Title>
    <URL>https://www.microsoft.com/technet/scriptcenter/resources/qanda/oct06/hey1009.mspx</URL>
  </Script>
  <Script>
    <Category>Microsoft Office</Category>
    <Subcategory>Microsoft Word</Subcategory>
    <Keyword>hyperlinks</Keyword>
    <Title>How Can I Change an Existing Hyperlink in a Microsoft Word Document?</Title>
    <URL>https://www.microsoft.com/technet/scriptcenter/resources/qanda/oct06/hey1016.mspx</URL>
  </Script>
  <Script>
    <Category>Enterprise Servers</Category>
    <Subcategory>Microsoft SQL Server</Subcategory>
    <Keyword>databases</Keyword>
    <Title>How Can I Create a Table in a SQL Server Database?</Title>
    <URL>https://www.microsoft.com/technet/scriptcenter/resources/qanda/oct06/hey1016.mspx</URL>
  </Script>
</Repository>

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

Ну ладно, вернемся к записям. В каждой записи есть следующие поля: <Category>, <Subcategory>, <Keyword>, <Title> и <URL>.

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

Set xmlDoc = CreateObject("Microsoft.XMLDOM")
xmlDoc.Async = "False"
xmlDoc.Load("C:\Scripts\Scripts.xml")

Set colNodes = xmlDoc.selectNodes _
("/Repository/Script/*")

For Each objNode in colNodes
   Wscript.Echo objNode.Text
Next

Обратите внимание, что имена тегов чувствительны к регистру. Нужно писать «/Repository/Script», а не «/repository/script» и не «/REPOSITORY/SCRIPT».

Разумеется, и сценарий не особо вас впечатлил. Но в том-то все и дело: выполнить поставленную задачу не так сложно, как может показаться вначале.

Как видно из кода, мы сначала создаем экземпляр объекта Microsoft.XMLDOM, COM-объект, используемый для работы с файлами XML. После этого мы устанавливаем для параметра Async значение False: тем самым мы гарантируем, что файл будет считан в память полностью, прежде чем выполнение сценария будет продолжено. Затем при помощи метода Load мы считываем файл C:\Scripts\Scripts.xml.

xmlDoc.Load("C:\Scripts\Scripts.xml")

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

Set colNodes = xmlDoc.selectNodes _
("/Repository/Script/*")

Обратите внимание на параметр, который передается методу selectNodes. Он представляет собой путь в файле XML. Наш файл XML имеет следующую структуру.

<Repository>
    <Script>
        <Category></Category>
        <Subcategory></Subcategory>
        <Keyword></Keyword>
        <Title></Title>
        <URL></URL>
    </Script>
</Repository>

Нужно ли об этом помнить? Да, нужно. При работе с файлами XML структура очень важна. Скажем, как получить отдельные свойства записи? Это просто. Мы переходим к тегу <Repository>, за которым идет тег <Script>. А после него находим теги отдельных свойств. Заметили? Это как раз тот путь, который указан в параметре, который передается методу selectNodes. После пути стоят косая черта и звездочка (/*). Это означает, что нужно выбрать все свойства данной записи. Другими словами, возвращаются все свойства всех записей в базе данных — подобным образом действует запрос «Select * From» в инструментарии управления Windows® (Windows® Management Instrumentation, WMI).

Примечание. Попробуйте запустить сценарий, приведенный в качестве образца, и сразу станет ясно, что именно мы имеем в виду.

Остальная часть сценария понятна. Создается цикл For Each, при выполнении которого обходятся все записи и выводятся значения свойства Text для каждой записи и для каждого свойства.

For Each objNode in colNodes
   Wscript.Echo objNode.Text
Next

Это проще, чем гоняться за машинами.

Конечно, если бы нам нужно было только лишь вывести все содержимое файла, мы могли бы обойтись без XML и использовать FileSystemObject. (Может, это было бы чуть сложнее, но ненамного.) А вот если мы собирались удивить вас и убедить в том, что XML — штука и впрямь удобная, пожалуй, наша цель еще не достигнута.

Посмотрим, что еще можно сделать. В первом сценарии мы получали на выходе все значения свойств для всех записей. Замечательно. А если нам надо получить только заголовок сценария? Мы сможем это сделать? Ммм... секундочку...

Да, Люси говорит, что для этого нужно всего лишь изменить команду selectNodes — добавить в конце пути интересующее нас свойство. Другими словами, нужно написать так.

    Set colNodes=xmlDoc.selectNodes _    ("/Repository/Script/Title")

Как выясняется, в первом случае мы получали все свойства только потому, что использовали подстановочный знак (*). А теперь мы получаем только значение свойства Title. Почему? Потому что мы сами указали, что выдавать нужно значения только этого свойства.

Примечание. Посмотрите-ка, XML дает нам в точности то, что мы просим. Люси, ты уж не обижайся, но кажется, лучший друг человека — это XML!

Разумеется, можно получать и значения нескольких свойств. Например, если изменить команду следующим образом, она будет возвращать значения Title и URL.

    Set colNodes=xmlDoc.selectNodes _    ("/Repository/Script/(Title | URL)")

Синтаксис может показаться неуклюжим, но как только вы привыкните к нему, то согласитесь, что он не так уж плох. Сначала в команде указывается родительская часть пути — точно так же, как мы делали до этого.

    /Repository/Script/

Затем добавляется пара скобок, в которых указываются свойства, значения которых должны возвращаться. (Обратите внимание, что между отдельными свойствами ставится вертикальная черта.) То есть, надо написать следующее.

    (Title | URL)

Как мы и предупреждали, это выглядит нелепо, но зато работает.

И кстати, число возвращаемых свойств двумя не ограничивается. Вы можете добавлять свойства, разделяя их вертикальной чертой, сколько вам заблагорассудится. Например, следующая команда возвращает три значения (Title, URL и Keyword).

    Set colNodes=xmlDoc.selectNodes _    ("/Repository/Script/(Title | URL |          Keyword)")

Здорово, правда?

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

    Set colNodes=xmlDoc.selectNodes _
    ("/Repository/Script " & _
    "[Keyword = ‘databases’]")

Синтаксис и здесь выглядит немного странным. Но если разобраться, он на самом деле прост и четок. После родительского пути в квадратных скобках указывается часть, задающая условие отбора (например «x равно y») — она эквивалентна разделу Where.

/Repository/Script [Keyword = ‘databases’]

Как легко догадаться, указанное условие означает: «Возвращать только записи, в которых атрибут Keyword имеет значение databases». Обратите внимание, что слово databases заключено в одинарные кавычки. Они там не для красоты — это требование синтаксиса. Если заключить слово, задающее условие отбора, в двойные кавычки, при попытке запустить сценарий появится сообщение о синтаксической ошибке.

Примечание. Почему? Помните, что вся строка запроса — "/Repository/Script [Keyword = ‘databases’]" — заключена в двойные кавычки?

При фильтрации данных можно использовать не только знак равенства (=). В условии отбора может стоять знак больше (>) или меньше (<), а также всеми любимые меньше или равно (<=) и больше или равно (>=). Также можно использовать отрицание — поставить восклицательный знак перед любым из перечисленных знаков. Например, следующая команда запрашивает все сценарии, в которых значение элемента Keyword не равно databases (обратите внимание на знак !=).

    Set colNodes=xmlDoc.selectNodes _
    ("/Repository/Script " & _
    "[Keyword != ‘databases’]")

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

    Set colNodes=xmlDoc.selectNodes _
    ("/Repository/Script " & _
    "[Keyword = ‘databases’]/Title")

Если все сделано правильно, при помощи такой команды возвращается только свойство Title для всех записей, элемент Keyword которых имеет значение databases. Давайте проверим, работает ли наш сценарий.

    How Can I Print a Microsoft Access Report?
    How Can I Compact a Microsoft Access Database?
    How Can I Create a Table in a SQL Server     Database?

Теперь посмотрим, можно ли добавить еще одну команду — и с чистым сердцем пойдем на перекур. Мы уже многому научились, и все же сценарий по-прежнему выдает больше записей, чем нужно. Предыдущая команда возвращает как сценарии Microsoft® Access®, так и сценарии Microsoft SQL Server™. (Почему? Потому что в каждом из них элемент Keyword имеет значение databases.) А если нужно получить только сценарии, в которых элемент Keyword имеет значение databases, а элемент Subcategory — значение Microsoft SQL Server? Можно ли отбирать записи по нескольким критериям?

Например, так.

    Set colNodes=xmlDoc.selectNodes _
    ("/Repository/Script " & _
    "[Keyword = ‘databases’ and " & _
    "Subcategory = ‘Microsoft SQL Server’]")

Тут использован все тот же базовый синтаксис, только введено два отдельных критерия (а именно, Keyword = ‘databases’ и Subcategory = ‘Microsoft SQL Server’), связанные оператором AND.

Примечание. Да, верно. У нас файл XML совсем маленький, и можно было просто запросить все сценарии, в которых элемент Subcategory имеет значение Microsoft SQL Server. Но это не так интересно и совсем не так поучительно.

Также можно использовать оператор OR. Допустим, есть большой набор сценариев Microsoft Office, в которых элемент Subcategory имеет значение Microsoft Excel®, Microsoft PowerPoint®, Microsoft Outlook® и т. д. Можно ли получать на выходе только сценарии, относящиеся к подкатегориям Microsoft Word и Microsoft Access? Абсолютно точно можно.

    Set colNodes=xmlDoc.selectNodes _
    ("/Repository/Script " & _
    "[Subcategory = ‘Microsoft Word’ " & _
    "or Subcategory = ‘Microsoft Access’]")

Ну что, совсем другое дело? Вряд ли эта статья в корне изменила всю вашу жизнь, но есть шанс, что рано или поздно станет весьма удобно использовать подобные запросы к файлам XML. Решать вам. Можно изучить некоторые основные приемы работы с XML, а можно и дальше гоняться за автомобилями. Все зависит только от вас. (Если вы выберете второй вариант и встретите Люси, ту псину, живущую в конце улицы, передавайте ей привет от Scripting Guys. И попросите ее держаться подальше от нашего двора!)

Эй, программист! — ежедневные выпуски

Вы только что изучили статью из раздела «Эй, программист!», вышедшую в этом месяце. Вы считаете ее лучшей технической статьей из всех, что вы когда-либо читали. А может быть, вы вообще считаете ее лучшим из того, что когда-либо было написано. Вы часами простаиваете у почтового ящика в ожидании следующего выпуска журнала TechNet, потому что вам не терпится прочесть еще одну такую же статью.

Так чего же вы ждете? Вы хотите узнать еще что-нибудь про соседскую собаку Люси? Почитать про новые подвиги Scripting Son? А вы знаете, как прошел Turducken Bowl в прошлом году? Все это вы найдете в ежедневных — да, вы не ослышались, ежедневных — выпусках статей «Эй, программист!». С понедельника по пятницу (за исключением государственных праздников и выходных у Scripting Guys) вы сможете получать самую подробную информацию о студенческих бейсбольных, футбольных и баскетбольных матчах. Вам может встретиться даже местный прогноз погоды. (Ну да, он будет местным только если вы живете в Редмонде, но покажите мне человека, которому неинтересно, что в Редмонде творится с погодой?)

А кроме того, каждый день вы будете узнавать что-то новое о программировании. В этих увлекательных текстах на самом деле есть информация о том, как писать сценарии. В каждой статье Scripting Guys отвечают на реальные вопросы, присланные им реальными, как они надеются, людьми. Ежедневные выпуски статей можно прочесть на веб-узле microsoft.com/technet/scriptcenter/resources/qanda. Scripting Guys уже ответили на сотни вопросов, и скопился довольно большой архив — кладезь ценной информации по написанию сценариев (microsoft.com/technet/scriptcenter/resources/qanda/hsgarch.mspx).

У вас появился вопрос к Scripting Guys? Если вы отправите его по адресу scripter@microsoft.com, есть небольшой шанс получить ответ. (Если вопрос не отправить, шансов получить ответ нет вообще, так что вы ничем не рискуете. Здесь вероятность выигрыша побольше, чем в лотерее.)

Сотрудники группы Microsoft Scripting Guys работают... ну хорошо, числятся в корпорации Майкрософт. Когда они не играют в бейсбол, не тренируют бейсбольную команду, не смотрят бейсбол и не занимаются другими делами, они ведут проект Script Center в журнале TechNet. Веб-узел проекта: www.scriptingguys.com (на английском языке).

© 2008 Корпорация Майкрософт и компания CMP Media, LLC. Все права защищены; полное или частичное воспроизведение без разрешения запрещено.