Код разметки HTML

kod razmetki html Сценарии на стороне сервера

Встроенная в сценарий и генерируемая им разметка HTML разбросана по всей программе в вызовах функции print, из-за чего трудно осуществлять значительные изменения структуры веб-страницы или передать разработку веб-страницы непрограммистам.

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

Шаг 1: совместное использование объектов разными страницами — новая форма ввода

Первые две из перечисленных выше проблем, осложняющие сопровождение, можно снять с помощью простого преобразования. Решение заключается в динамической генерации главной страницы сценарием вместо использования готового файла HTML. Сценарий может импортировать имя поля ввода и значения для списка выбора из общего файла модуля на языке Python, совместно используемого сценариями генерации главной страницы и страницы ответа. При изменении списка выбора или имени поля в общем модуле автоматически изменяются оба клиента. Сначала переместим совместно используемые объекты в файл общего модуля, как показано в примере 15.19.

Пример 15.19. PP4E\Internet\Web\cgi-bin\languages2common.py

общие объекты, совместно используемые сценариями главной страницы и страницы ответа; при добавлении нового языка программирования достаточно будет изменить только этот файл.

inputkey = language # имя входного параметра

hellos = {

‘Python’: r" print(‘Hello World’) ",

‘Python2’: r" print ‘Hello World’ ",

‘Perl’: r’ print "Hello World\n"; ‘,

‘Tcl’: r’ puts "Hello World" ‘,

‘Scheme’: r’ (display "Hello World") (newline) ‘,

‘SmallTalk’: r" ‘Hello World’ print. ",

‘Java’: r’ System.out.println("Hello World"); ‘,

‘C’: r’ printf("Hello World\n"); ‘,

‘C++’: r’ cout << "Hello World" << endl; ‘,

‘Basic’: r’ 10 PRINT "Hello World" ‘,

‘Fortran’: r" print *, ‘Hello World’ ",

‘Pascal’: r" WriteLn(‘Hello World’); "

}

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

Обратите внимание, что этот модуль находится в том же каталоге cgi-bin, что и сценарии CGI, которые его используют. Это упрощает процедуру импортирования — модуль будет найден в текущем рабочем каталоге сценария без дополнительной настройки пути поиска модулей. В целом внешние ссылки в сценариях CGI разрешаются следующим образом:

     Им пор тиро ва ние модулей выполняется относительно текущего рабочего каталога сценария CGI (cgi-bin) с учетом всех настроек пути поиска, выполненных при запуске сценария.

     При использовании ми ни маль ных адресов URL поиск страниц и сценариев, указанных в ссылках и атрибутах action форм HTML, выполняется как обычно относительно местоположения предшествующей страницы. Для сценариев CGI такие минимальные адреса URL откладываются относительно местоположения самого сценария.

     Поиск файлов, имена которых указываются в параметрах запросов и передаются сценариям, обычно выполняется относительно каталога, содержащего сценарий CGI (cgi-bin). Однако на некоторых платформах и серверах пути могут откладываться относительно каталога веб-сервера. В нашем локальном веб-сервере действует последний вариант.

Чтобы убедиться в том, что это действительно так, посмотрите и запустите сценарий CGI, находящийся в пакете примеров и доступный по адресу URL http://localhost/cgi-bin/test-context.py: при выполнении в Windows под управлением нашего локального веб-сервера он способен импортировать модули, находящиеся в его собственном каталоге, но поиск файлов выполняется относительно родительского каталога, откуда запущен веб-сервер (вновь создаваемые файлы появляются здесь). Ниже приводится программный код этого сценария на случай, если вам будет интересно узнать, как отображаются пути в вашем веб-сервере и на вашей платформе. Такая зависимость интерпретации относительных путей к файлам от типа веб-сервера, возможно, и не способствует переносимости, но это всего лишь одна из множества особенностей, отличающихся для разных серверов:

import languages2common # из моего каталоге

f = open(‘test-context-output.txt’, ‘w’) # в каталоге сервера ..

f.write(languages2common.inputkey)

f.close()

print(‘context-type: text/html\n\nDone.\n’)

Далее в примере 15.20 мы перепишем главную страницу в виде выполняемого сценария, помещающего в разметку HTML ответа значения, импортированные из файла общего модуля, представленного в предыдущем примере.

Пример 15.20. PP4E\Internet\Web\cgi-bin\languages2.py

#!/usr/bin/python

разметка HTML главной страницы генерируется сценарием Python, а не готовым файлом HTML; это позволяет импортировать ожидаемое имя поля ввода и значения таблицы выбора языков из общего файла модуля Python; теперь изменения нужно выполнять только в одном месте, в файле модуля Python;

REPLY = """Content-type: text/html

<html><title>Languages2</title>

<body>

<h1>Hello World selector</h1>

<Р>Эта страница похожа на страницу в файле <a href="../languages.html"> languages.html</a>, но генерируется динамически с помощью сценария CGI на языке Python, используемый здесь список выбора и имена полей ввода импортируются из общего модуля Python на сервере. При добавлении новых языков достаточно будет изменить только общий модуль, потому что он совместно используется сценариями, производящими страницы ответа.

Чтобы увидеть программный код, генерирующий эту страницу и ответ, щелкните <a href="getfile.py?filename=cgibin\languages2.py">3gecb</a>, <a href="getfile.py?filename=cgibin\languages2reply.py">3gecb</a>, <a href="getfile.py?filename=cgibin\languages2common.py">3gecb</a> и <a href="getfile.py?filename=cgibin\\formMockup.py">3gecb</a>.</P> <hr>

<form method=POST action="languages2reply.py">

<P><B>Select a programming language:</B>

<P><select name=%s>

<option>All %s

<option>Other

</select>

<P><input type=Submit>

</form>

</body></html>

from languages2common import hellos, inputkey

options = []

for lang in hellos: # можно было бы отсортировать

# по ключам

options.append(‘<option>’ + lang) # обернуть таблицу ключей # разметкой HTML

options = ‘\n\t’.join(options)

print(REPLY % (inputkey, options)) # имя поля и значения из модуля

Пока не обращайте внимания на гиперссылки getfile в этом файле, их значение будет объяснено в следующем разделе. Обратите, однако, внимание, что определение страницы HTML превратилось в строку Python (с именем REPLY), включающую спецификаторы форматирования %s, которые будут замещены значениями, импортированными из общего модуля. В остальных отношениях оно аналогично содержимому оригинального файла HTML. При переходе по адресу URL этого сценария выводится сходная страница, изображенная на рис. 15.25. Но на этот раз страница создается в процессе выполнения сценария на стороне сервера, который заполняет раскрывающийся список выбора значениями из списка ключей в общей таблице синтаксиса. Используйте пункт меню броузера View Source (Исходный код страницы или Просмотр HTML-кода), чтобы увидеть сгенерированную разметку HTML — она практически идентична содержимому файла HTML в примере 15.17, хотя порядок следования языков в списке может отличаться, что обусловлено особенностями поведения словарей.

Рис. 15.25. Альтернативная главная страница, созданная сценарием languages2.py

 

Одно замечание, касающееся сопровождения: содержимое строки REPLY с шаблоном разметки HTML в примере 15.20 можно было бы загружать из внешнего текстового файла, чтобы над ним можно было работать независимо от сценария на языке Python. Однако в целом работать над внешними текстовыми файлами ничуть не проще, чем над сценариями Python. Фактически сценарии Python являются текстовыми файлами, и это важная особенность языка — сценарии Python легко можно изменять непосредственно в установленной системе без необходимости выполнять этапы компиляции и связывания. Однако внешние файлы HTML могут храниться в системе управления версиями отдельно, что, возможно, придется учитывать в вашей организации.

Шаг 2: многократно используемая утилита имитации формы

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

Пример 15.21. PP4E\Internet\Web\cgi-bin\formMockup.py

Инструменты имитации результатов, возвращаемых конструктором cgi.

FieldStorage(); удобно для тестирования сценариев CGI из командной строки

class FieldMockup: # имитируемый объект с входными данными

def __init__(self, str):

self.value = str

def formMockup(**kwargs): # принимает аргументы в виде поле=значение

mockup = {} # множественный выбор: [value,…]

for (key, value) in kwargs.items():

if type(value) != list: # простые поля имеют атрибут .value mockup[key] = FieldMockup(str(value))

else: # поля множественного выбора

# являются списками

mockup[key] = [] # что сделать: добавить поля выгрузки файлов

for pick in value:

mockup[key].append(FieldMockup(pick))

return mockup

def selftest():

#   использовать эту форму, если поля можно определить жестко

form = formMockup(name=’Bob’, job=’hacker’,

food=[‘Spam’, ‘eggs’, ‘ham’])

print(form[‘name’].value)

print(form[‘job’].value)

for item in form[‘food’]:

print(item.value, end=’ ‘)

#   использовать настоящий словарь, если значения ключей находятся

#   в переменных или вычисляются

print()

form = {‘name’: FieldMockup(‘Brian’),

‘age’: FieldMockup(38)} # или dict()

for key in form.keys():

print(form[key].value)

if __name__ == ‘__main__’: selftest()

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

C:\\PP4E\Internet\Web\cgi-bin> python formMockup.py Bob hacker

Spam eggs ham

38

Brian

Так как теперь реализация имитирующего класса находится в модуле, ею можно будет воспользоваться всякий раз, когда потребуется протестировать сценарий CGI в автономном режиме. Для иллюстрации в примере 15.22 приводится переработанная версия сценария tutor5.py, представленного ранее, в котором использована утилита имитации формы для моделирования полей ввода. Если бы мы спланировали это заранее, то таким способом могли бы проверить этот сценарий безо всякого подключения к Сети.

Пример 15.22. PP4E\Internet\Web\cgi-bin\tutor5_mockup.py

#!/usr/bin/python

выполняет логику сценария tutor5 с применением formMockup

вместо cgi.FieldStorage()

для проверки: python tutor5_mockup.py > temp.html и открыть temp.html

from formMockup import formMockup

form = formMockup(name=’Bob’,

shoesize=’Small’, language=[‘Python’, ‘C++’, ‘HTML’], comment=’ni, Ni, NI’)

# остальная часть сценария, как в оригинале, кроме операции присваивания form

При этом, конечно, предполагается, что этот модуль находится в пути поиска модулей Python. Так как по умолчанию интерпретатор ведет поиск импортируемых модулей в текущем каталоге, это условие всегда выполняется без изменения sys.path, если все файлы находятся в главном веб-каталоге. Для других приложений может потребоваться добавить этот каталог в переменную окружения PYTHONPATH или использовать синтаксис импортирования пакетов (с указанием пути к каталогу).

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

C:\\PP4E\Internet\Web\cgi-bin> python tutor5_mockup.py

Content-type: text/html

<TITLE>tutor5.py</TITLE>

<H1>Greetings</H1>

<HR>

<H4>Your name is Bob</H4>

<H4>You wear rather Small shoes</H4>

<H4>Your current job: (unknown)</H4>

<H4>You program in Python and C++ and HTML</H4>

<H4>You also said:</H4>

<P>ni, Ni, NI</P>

<HR>

При запуске из броузера будет выведена страница, показанная на рис. 15.26. Значения полей ввода здесь жестко определены, что похоже по духу на расширение tutor5, в котором входные параметры встраивались в конец адреса URL внутри гиперссылки. Здесь они поступают от объектов имитации формы, создаваемых в сценарии ответа, которые можно изменить только редактированием сценария. Но поскольку программный код на языке Python выполняется немедленно, модификация сценария Python во время цикла отладки происходит со скоростью ввода с клавиатуры.

Рис. 15.26. Страница ответа с имитируемыми входными данными

Шаг 3: объединим все вместе — новый сценарий ответа

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

Пример 15.23. PP4E\Internet\Web\cgi-bin\languages2reply.py

#!/usr/bin/python

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

import cgi, sys

from formMockup import FieldMockup # имитация полей ввода

from languages2common import hellos, inputkey # общая таблица, имя параметра debugme = False

hdrhtml = """Content-type: text/html\n

<TITLE>Languages</TITLE>

<H1>Syntax</H1><HR>"""

langhtml = """

<H3>%s</H3><P><PRE>

%s

</PRE></P><BR>"""

def showHello(form): # разметка HTML для одного языка

choice = form[inputkey].value # экранировать имя языка тоже try:

print(langhtml % (cgi.escape(choice),

cgi.escape(hellos[choice])))

except KeyError:

print(langhtml % (cgi.escape(choice),

"SorryI don’t know that language"))

def main():

if debugme:

form = {inputkey: FieldMockup(sys.argv[1])} # имя в командной строке else:

form = cgi.FieldStorage() # разбор действительных входных данных print(hdrhtml)

if not inputkey in form or form[inputkey].value == ‘All’:

for lang in hellos.keys():

mock = {inputkey: FieldMockup(lang)} # здесь не dict(n=v)! showHello(mock)

else:

showHello(form) print(‘<HR>’)

if __name__ == ‘__main__’: main()

Если в глобальной переменной debugme установить значение True, сценарий можно будет тестировать из командной строки:

C:\\PP4E\Internet\Web\cgi-bin> python languages2reply.py Python

Content-type: text/html

<TITLE>Languages</TITLE>

<H1>Syntax</H1><HR>

<H3>Python</H3><P><PRE>

print(‘Hello World’)

</PRE></P><BR>

<HR>

При вызове этого сценария из страницы на рис. 15.25 или путем ввода адреса URL с параметрами запроса вручную возвращаются те же страницы, которые мы видели при использовании первоначальной версии этого примера (не станем повторять их снова). Данное преобразование изменило архитектуру программы, но не ее интерфейс пользователя. Однако теперь обе страницы, ввода данных и ответа, создаются сценариями CGI, а не с помощью статических файлов HTML.

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

Это различие в порядке следования элементов списка обусловлено тем, что данная версия зависит от порядка в списке ключей словаря Python, а не от фиксированного списка в файле HTML. Словари, если вы помните, упорядочивают свои записи так, чтобы скорейшим образом осуществлять их выборку. Если вы хотите, чтобы список выбора был более предсказуем, просто отсортируйте список ключей с помощью функции sort, появившейся в версии Python 2.4:

for lang in sorted(hellos): # итератор словаря вместо метода .keys() mock = {inputkey: FieldMockup(lang)}

Использованная литература:

Марк Лутц — Программирование на Python, 4-е издание, II том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий