AppleScript — это совсем не страшно

Реальная задача

В предыдущих статьях цикла Вы познакомились с языком AppleScript, научились пользоваться редактором скриптов, находить описания объектов, их свойств и методов в словарях программ. Рассмотрели мы и некоторые простые примеры. Однако AppleScript способен на гораздо большее. Скажем, объединить для решения поставленной задачи несколько различных программ. Этим мы сегодня и займемся.

Примечание: в данной статье использован BBEdit 5. В шестой версии словарь программы был изменен, поэтому описанный скрипт без изменений работать с ней не сможет.

Ставим задачу

Вполне реальная ситуация: Вы ведете подборку ответов на часто задаваемые вопросы (FAQ) и размещаете ее на web-сайте. Вначале их немного, и вполне можно обойтись обычным текстовым редактором. Но время идет, вручную сортировать вопросы, размещать их в нужном месте файла, строить содержание становится все труднее. Конечно, идеальным способом было бы организовать базу данных и строить страницы «на лету». Да вот только сайт размещен на одном из «халявных» хостингов, так что приходится обходиться статическими html-документами. Как быть?

Для сбора информации используем базу данных, сделанную с помощью программы FileMaker Pro{сноска: к сожалению, мне не известен способ полноценного управления базами данных в AppleWorks из AppleScript}. Структура у нее предельно проста: единственная таблица с тремя полями типа «text»: Category, Question и Answer. Причем, для категории должен быть задан список допустимых значений.

Определение полей базы данных

Список значений поля «Категория»

Для публикации накопленная информация будет переноситься в html-файлы. Их будет столько же, сколько категорий — ведь вопросов много, а большой документ и загружается долго, и ориентироваться в нем трудно. Строить страницы, вообще говоря, можно почти в любом скриптуемом текстовом редакторе. Мы же воспользуемся одним из наиболее популярных — BBEdit.

Определим требования к генерируемым web-страницам:

  1. Страница должна включать заголовок (название категории), содержание (список вопросов со ссылками) и основную часть.
  2. Название категории должно также быть включено в титульную строку.
  3. В основном тексте необходимо обеспечить выделение каким-либо способом начала каждого вопроса и ответа.
  4. У пользователя должна быть возможность возврата к содержанию.
  5. Переводы строк заменяются тегом <br>.
  6. Если в ответе упоминается тот или иной internet-ресурс, его URL преобразуется в ссылку.
  7. Ну, и, естественно, получившийся файл должен полностью соответствовать спецификациям HTML 4.

Свойства. Свойства? Свойства!

При формировании html-файлов мы будем вставлять в текст большое число различных элементов разметки. Именно они определяют вид получающихся страниц. Вряд ли Вы хотите, чтоб дизайн сайта был раз и навсегда заданным. Но при каждом изменении внимательно просматривать весь скрипт, стараясь не пропустить ничего существенного, крайне неудобно. Воспользуемся хорошо знакомым любому программисту приемом — вынесем определения этих строк в самое начало программы, определив «свойства» нашего будущего скрипта. Помните, есть в AppleScript такие переменные (property), сразу получающие заданные значения?

Итак...

property DBname : "FAQ.fmp" -- имя файла базы данных
  

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

Теперь укажем все необходимые фрагменты html-кода. Элементы формирующие заголовочную часть:

property headerText1 : ¬
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional¬
//EN\"\r\"http://www.w3.org/TR/REC-html40/loose.dtd\">\r¬
<html><head><title>FAQ: "
property headerText2 : "</title></head>\r¬
<body bgcolor=\"#ffffff\">\r<h1>"
property headerText3 : "</h1>"
  

Обратите внимание: некоторые теги должны включать кавычки. Для AppleScript это — спецсимвол, поэтому в строке они указываются в виде «\"». Также здесь встречается символ перевода строки — «\r». Вообще-то без него вполне можно обойтись. Это даже на несколько байт уменьшит размер файла. Но если Вы предполагаете выполнять какое-либо последующее «ручное» редактирование, экономить на нем не стоит.

Заключительные теги документа:

property footerText : "</body></html>"
  

Строки, оформляющие список-содержание (на начальной устанавливаем «якорь» — метку для возврата):

property beginTOC : "<a name=\"toc\"></a><ul>\r"
property endTOC : "</ul>\r"
  

Оформление основного текста. Я использовал просто слова «вопрос» и «ответ», но, конечно же, тут могут быть и ссылки на графические элементы.

property beginQ : "<p><b>Вопрос: </b>"
property endQ : "</p>"
property beginA : "<div><b>Ответ: </b>"
  

После каждого ответа вставляем гиперссылку на начало содержания:

property endA : "</div>\r¬
<p align=\"right\"><a href=\"#toc\">К началу</a></p>"
  

Еще одно свойство я ввел просто для удобства чтения текста скрипта:

property CR : "\r"
  

Тут же объявим несколько глобальных переменных:

global HTMLname -- имя очередного html-файла
global headerText -- текст, предшествующий содержанию
global Question -- текст очередного вопроса
global Answer -- текст очередного ответа
global qCounter -- счетчик вопросов
global sCounter -- счетчик разделов (категорий)
  

Теперь приступим к наиболее сложной части — построению алгоритма.

Сыграем в кубики

На этот раз задача перед нами не столь проста, как раньше: предстоит работать с несколькими документами в двух различных программах, выполняя по ходу дела не одно преобразование текста. Как же составить скрипт и не запутаться? Ответ очевиден: нужно разбить большую задачу на несколько подзадач, найти и описать алгоритм решения каждой из них, а затем из получившихся «кубиков» построить необходимую программу. Благо, в AppleScript имеется специальная конструкция — обработчик (см. Часть 2).

Чаще всего программисты используют так называемый метод проектирования «сверху-вниз». При этом сперва составляется «головная» программа, описывающая последовательность выполнения процедур-«кубиков». В этот момент важно только, ЧТО делает каждый модуль. А вот КАК он это делает, решится на следующем этапе — при «детализации».

Используют и обратный метод — сборочный или «проектирования снизу-вверх». Он удобен, когда у Вас уже есть «библиотека» готовых процедур.

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

tell DBaccess
 set listSection to listSec(DBname)
  

Получим список всех разделов FAQ. Как — будет описано в процедуре listSec.

 set sCounter to 1
  

Это — счетчик разделов. Он нам пригодится для формирования имен html-файлов. Сперва мы устанавливаем его в единицу (можно и в ноль — кому как больше нравится), а затем после записи каждой страницы будем увеличивать.

Все остальные действия будем повторять с каждой категорией из списка:

 repeat with section in listSection
  

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

  set listFAQ to listRec(DBname, section)
  if listFAQ is not {} then
  

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

     set HTMLname to "faq" & (sCounter as string) & ".html"
     set headerText to headerText1 & section & headerText2 & section & headerText3
  

Создаем основной и вспомогательный текстовые файлы, заносим в них «стандартные» элементы:

     MakeDest()
     MakeScrap()
  

Обнуляем счетчик вопросов (номер вопроса используем, чтобы генерировать уникальные метки для гиперссылок,- абсолютно так же как поступили с именами файлов):

     set qCounter to 0
  

Из каждой записи списка берем вопрос и ответ, записываем их (с необходимыми тегами разметки) в содержание и основной текст:

     repeat with FAQ in listFAQ
      set Question to item 2 of FAQ
      set Answer to item 3 of FAQ
      writeTOC(Question)
      writeFAQ(Question, Answer)
      set qCounter to qCounter + 1
     end repeat
  

Идет запись в «основную» часть документа

Выполняем сборку и окончательную обработку страницы, затем наращиваем счетчик:

     FinalTask()
     set sCounter to sCounter + 1
  end if
 end repeat
end tell
  

Ну, вот. Головная программа готова. Правда, просто? Но, чтобы скрипт заработал, придется еще потрудиться. Заметьте, в операторе Tell в качестве объекта был указан «DBaccess». Это имя не прикладной программы, как было в предыдущих примерах, а скриптовой библиотеки. Она должна включать все используемые процедуры. А оформляется такая структура с помощью специального оператора:

script DBaccess
 процедуры
end script
  

Описанием процедур мы сейчас и займемся.

База данных, ау!

Перво-наперво, как Вы помните, предстоит узнать, вопросы по каким темам могут быть в нашей базе. Это сделать очень просто — ведь допустимые значения поля Category у нас заданы списком, остается только его прочесть. Открываем базу данных:

 on listSec(DB)
  tell application "FileMaker Pro"
     open DB
     go to database DB
  

Эта строка необходима, даже если открыта только одна БД.

Делаем все записи видимыми, так как «скрытые» не будут учтены при обработке.

     show records
     set Sect to choices of field 1 of document DB
  end tell
  return Sect
 end listSec
  

Исходная база данных

Немногим сложнее получить список записей заданной категории. Поскольку every record выдает только «видимые» записи, достаточно предварительно выполнить поиск по полю Category. Дополнительно здесь можно отсортировать вопросы по алфавиту:

 on listRec(DB, section)
  tell application "FileMaker Pro"
     go to database DB
     try
      show (every record of database DB whose first cell = section)
      sort first layout by field "Question"
      set Rec to every record of document DB
     on error
      set Rec to {}
     end try
  end tell
  return Rec
 end listRec
  

Заметьте: в случае отсутствия записей указанной категории значением процедуры станет пустой список. Это делается с помощью обработчика ошибок (см Часть 2).

Заготовки для странички

Теперь займемся построением собственно web-страницы. В основной файл нужно сразу вписать весь текст, предшествующий содержанию, а также теги, открывающие список:

 on MakeDest()
  tell application "BBEdit 5.1"
     activate
     make new window with properties {name:HTMLname,  ¬
       contents:headerText & CR & beginTOC & CR}
  end tell
 end MakeDest
  

Во вспомогательный файл (имя для него можно задать совершенно произвольно) записываем пока только теги, завершающие список:

 on MakeScrap()
  tell application "BBEdit 5.1"
     make new window with properties {name:"Scrap", contents:endTOC & CR}
  end tell
 end MakeScrap
  

Формируем строку содержания и записываем ее в файл. Строка, кроме текста вопроса, содержит гиперссылку и помещается в теги <li>…</li>. Имя метки включает номер вопроса. Обратите внимание: здесь вновь используется преобразование типа — «as string».

 on writeTOC(qText)
  set itemText to "<li><a href=\"#a" & (qCounter as string) & "\">" & qText ¬
     & "</a></li>"
  tell application "BBEdit 5.1"
     tell window HTMLname
      set index to 1
      insert text itemText & CR
     end tell
  end tell
 end writeTOC
  

Почти также поступаем и с основным текстом. Отличия здесь, во-первых, в том, что кроме строки-вопроса есть еще и ответ. А во-вторых, само построение строк несколько более сложное. Ведь мы вынесли теги оформления этой части в «свойства». {сноска: вставка текста всегда происходит в активное — «верхнее» — окно, активизация его в BBEdit делается командой «set index to 1»}

 on writeFAQ(qText, aText)
  set itemText1 to beginQ & "<a name=\"a" & (qCounter as string) & "\"></a>" &  ¬
    qText & endQ
  set itemText2 to beginA & aText & endA
  tell application "BBEdit 5.1"
     tell window "Scrap"
      set index to 1
      insert text itemText1 & CR & itemText2 & CR
     end tell
  end tell
 end writeFAQ
  

Завершающие штрихи

Последний этап генерации страницы — объединение двух ее частей. Но перед этим необходимо завершить разметку основной части:

on FinalTask()
  tell application "BBEdit 5.1"
     tell window "Scrap"
  

Перед каждым переводом строки вставляем тег <br>:

      replace Every Occurrence searching for CR using "<br>" & CR with start at top
  

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

      replace Every Occurrence searching for "</p><br>" using "</p>"  ¬
        with start at top
      replace Every Occurrence searching for "</div><br>" using "</div>" ¬
        with start at top
      replace Every Occurrence searching for "</li><br>" using "</li>" ¬
        with start at top
      replace Every Occurrence searching for "l><br>" using "l>" with start  ¬
        at top
  

Теперь превратим в гиперссылки все URL web-страниц, встречающиеся в тексте:

      replace Every Occurrence searching for "http://[-a-z0-9./]*" using  ¬
        "<a href=\"&\">&</a>" with start at top and grep
  

Обработку URL удалось свести к единственной команде благодаря использованию малоизвестного, но очень мощного механизма, реализованного в BBEdit — grep. Он подобен по действию аналогичной команде Unix, предназначенной для работы с «регулярными выражениями». В данном случае программа ищет текст, состоящий из «http://» и любого количества буквенно-цифровых символов, дефисов, точек и наклонных линий. Причем найденный фрагмент используется как часть нового текста.

Теперь, подготовив основной текст, сохраняем ссылку на него в переменной mainText, переходим в основной документ и окончательно «собираем» страницу:

      set mainText to its text
     end tell
     tell window HTMLname
      set index to 1
      insert text mainText & CR & footerText
     end tell
  

HTML-страница построена

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

     save window 1 to HTMLname
     close window 1
     close window "Scrap" saving no
  end tell
 end FinalTask
  

А вот так готовая страница выглядит в браузере

Вот и все. С готовым скриптом можно поступить по-разному. Например, поместить в папку «BBEdit Scripts» и вызывать из меню программы. Или сохранить в виде приложения — тогда он сам будет запускать и FileMaker, и BBEdit, чтобы выполнить необходимую обработку. Как дополнить скрипт, чтобы после выполнения задания эти программы автоматически закрывались — пусть будет маленьким «домашним заданием».


[Предыдущая][Содержание][Следующая]
[Новости][Макинтош][Информатика и ИТ][Об авторе]
Hosted by uCoz