Саммерфилд - Программирование на Python 3 (1077331), страница 83
Текст из файла (страница 83)
герогт тб ), атгсгатт тб=ха1.вах.вахе!па,оютеаттг( !по!беат.а!гога!! 1б). атгсгатт туре=ха1.вах.вахе(11в.ооотеаттг( тпс!беат.а!гога!1 туре), а1грогт=ха1.вах.вахо(11в.евсаре(!по!бел!.атгрогт), паггат!че="тп".)а!п(техтгггар.гггар( ха1.вах.вахе!>1в.евсаре( тпс1бепт.паггат1че.втг1р()), 70)))) тп.юг!те("</!по!беата>1п") гетега Тгче Как и прежде в этой главе„мы опустили блоки ехсерт и Т!па11у. 373 Запись и синтаксический анализ файлов ХМ1 При записи в файл используется кодировка ПТг-3, и ее необходимо указать в вызове встроенной функции ореп(). Строго говоря, кодировку можно и не указывать в объявлении <? ха1? > потому, что кодировка ()ТГ-8 используется по умолчанию, но мы предпочитаем делать это явно. Мы решили заключать значения атрибутов в кавычки, поэтому при добавлении данных об инциденте для обозначения строк в программном коде используются апострофы, благодаря чему отпала необходимость экранировать кавычки.
Функция вах.вахШ11в.свотеаттс() напоминает по своему действию функцию вах. вахШ11в. евсаре(), используемую для обработки текста ХМЬ, — тем, что она корректно экранирует символы «й», «(» и «>». Кроме того, она экранирует кавычки (если это необходимо) и возвращает готовую к использованию строку, уже заключенную в кавычки. По этой причине нам не потребовалось окружать кавычками идентификатор отчета и другие строковые значения атрибутов. Символы перевода строки, которые мы вставляем, и выравнивание текста комментария — это исключительно косметическое прихорашивание.
Сделано это только для того, чтобы содержимое файла проще было читать людям, поэтому их легко можно просто опустить. Запись данных в формате НТМ1, мало чем отличается от записи данных в формате ХМ1,. Программа сопиегп(лсИелтзру включает в себя функцию ехрогт Птв!() — в качестве простого примера такой возможности, однако мы не будем рассматривать ее, потому что в ней нет ничего нового, что действительно стоило бы показать. Синтаксический анализ файлов ХМ!. с помощью 5АХ (5!тр!е АР! аког ХМ!. — упрощенный АР! для ХМ~) В отличие от дерева элементов и РОМ, которые формируют документ ХМВ в памяти целиком, парсеры БАХ используют принцип последовательной обработки, реализация которого потенциально обладает более высокой скоростью работы и предъявляет более низкие требования к объему памяти. Однако преимущество в скорости можно не учитывать, так как реализации деревьев элементов и РОМ используют быстрый парсер ехрас.
Парсеры БАХ, когда встречают начальные теги, конечные теги и другие элементы ХМ1, извещают об этом посредством «событий парсинга». Чтобы иметь возможность обрабатывать интересующие нас события, мы должны создать соответствующий класс обработчика и реализовать в нем предопределенные методы, которые будут вызываться по соответствующим событиям. Наиболее часто в программах реализуется обработчик содержимого, хотя, когда возникает необходимость в более полном управлении процессом парсинга, можно предусмотреть и реализацию обработчиков ошибок других обработчиков.
374 Глава 7. Работа с файлами Ниже приводится полный программный код метода 1зрогт хв1 вах(). Он получился очень коротким благодаря тому, что основная работа выполняется классом 1пс1беп18ахНапб1е г: бе( 1арогт хэ1 вах(ве1Г, тт1епаае): (П = попе тгу. Папб1ег = 1пс1бептзахаапб1ег(ве1() рагвег = ха1.вах.захе рагвег() рагвег.ветСсптептнапб1ег(папб1ег) рагвег.рагве((11епаае) гетчгп Тгче ехсерт (епч!гопаептеггог, уа1пееггог, 1псгбептеггсг, ха1.вах.ЯАХРагвеЕхсерттап) ав егг: рг1пт("(0), тпрогт еггаг: (1)".тпгаат( пв.рата.аавепаае(вув.агру[0]), егг)) гетега Ра1ве Мы создали один обработчик, который будет использоваться нами, затем создали экземпляр парсера БАХ и передали ему в качестве обработчика содержимого обработчик, созданный непосредственно перед этим. После этого мы передали методу ра гве( ) парсера имя файла и вернули Тгпе, если в ходе анализа файла не возникло никаких ошибок.
Методу инициализации обработчика класса 1пс!беп18ахНапб1ег был передан объект ве1( (то есть объект класса 1пс[бептСа11ес1[оп, являющегося подклассом бтст). Обработчик удаляет всю прежнюю информацию об инцидентах и затем по мере разбора файла наполняет его новыми данными об инцидентах. По завершении процесса парсинга словарь будет содержать все прочитанные записи об инцидентах. с1авв 1пстбептвахНапб1ег(ха1. вах. Папб1ег. Соптеп1Напб1ег): бе( 1птт (ве1(, !пстбаптв). порет(). 1п)т () ве1(. бата = () ве)т, техт = "" ве1(.
1пстбептв = [пс1бептв ве1(. тпсшептв, с1еаг() Наш собственный класс обработчика должен наследовать соответствующий базовый класс. Тем самым гарантируется, что при отсутствии реализации некоторых методов (просто потому, что некоторые события парсинга нас не интересуют) будут вызываться методы базового класса, которые не делают ничего опасного.
В самом начале вызывается метод инициализации базового класса. Вообще, это желательно делать в любых подклассах, хотя для прямых наследников класса об] ест в этом нет необходимости (но и нет никакой опасности). Словарь ве1(. бата используется для хранения информации об инциденте, строка ве11. техт используется для хранения текста с названием аэропорта или комментария — в зависимости от того, Запись и синтаксический анализ файлов )<М[ какой элемент данных читается, и словарь ве]г.
!по!Сел!в является ссылкой на словарь |пссоепССо1]есС!оп, который будет дополняться обработчиком напрямую. (В качестве альтернативы можно было бы создать внутри обработчика независимый словарь и копировать его в конце вызовом методов О!сС. с1еаг() и д!сС. орсате().) Оес всагСЕ1епепт(ве1(, паве, ассгссиСев): ст паве == "!пссоепс": ве1(. баса = () Гог Кеу, ча1ое !п аССг!потев.!Сеав(): сг кеу == 'пасе'; ве1(. саса[кеу) = пасет!ае.пасе!!ве.всгрс!ае( ча1че, "%У-%а-%О").Овсе() е)!Г Кеу == "р!1оС регсепС ПОоГВ ОП СУРЕ".' ве1(. Оата[Кеу] = (1оаС(ча1че) е)с( Кеу == "рс1оС Сота1 Поогв". ве1(.
оаса[кеу] = !пс(ча1ое) е1ст Кеу == паспасг": ве)Г. Оата[хеу] = Ооо)(спт(ча)че)) е1ве: ве1(. оаса[кеу] = ча1че ве)Г. техт = "" Всякий раз, когда парсер встречает открывающий тег и его атрибуты, он вызывает метод хв1. вах. напс1ег. Сопсвпснапс1ег. в!а гсе1евепс(), которому передает имя тега и его атрибуты. В файле ХМЬ, содержащем информацию об авиационных инцидентах, имеются следующие открывающие теги: <!пс!сел!в>, который мы просто игнорируем; <!пс!сепс>, атрибуты которого помещаются в словарь ве1(. Ната; а также <а1грогс> и <паггас1че>, которые мы тоже игнорируем.
Всегда, когда встречается открывающий тег, мы очищаем строку ве1(. техт, потому что в формате файла ХМВ с информацией об авиационных инцидентах отсутствуют вложенные текстовые теги. Мы не предусматриваем обработку исключений в классе [пссг)епс8зхНапо1ег. В случае появления исключения оно будет передано вызывающему методу, в данном случае — методу !прог! ха1 вах(), который перехватит его и выведет соответствующее сообщение об ошибке. Оес епСЕ1еэепт(ве1(, папе): сг паве == "!пс!пепс": 1( 1еп(ве1(. пата) .'= 9; га1ве !пс1оепсеггог("асвв1по паса") спс1пепт = 1псссепС(**ве1Г. баСа) ве1(.
1пс10епсв(!поспел!.герогс !о] = спсссепс е)!г паве сп ггогепвес(("а!гросс", "паггас!че")): ве1(. паса[папе] = ве1(, сехс,всыр() ве1(. сехс = "" Когда парсер встречает закрывающий тег, он вызывает метод хв1, вах, лапг)1ег.сопсепснапо1ег. ЕпсЕ1ввепс(). Если был достигнут конец записи 376 Глава 7. Работа с файлами об инциденте, все необходимые данные уже должны быть собраны, поэтому остается только создать новый объект 1пстбепт и добавить его в словарь с инцидентами. Если был обнаружен закрывающий тег текстового элемента, в словарь ве1Г. вата добавляется новый элемент с текстом, извлеченным к данному моменту. В конце метод очищает СтрОКу ВЕ1(. тЕхт, ПОдГОтаВЛИВая ЕЕ К даЛЬНЕйШЕМу ИСПОЛЬЗОВаНИЮ.
(Строго говоря, ее можно и не очищать, так как она очищается при обнаружении открывающего тега, но очистка может потребоваться при работе с некоторыми другими форматами ХМ1, например, где имеются вложенные теги.) сег спагастегв(ве1Ь техт); ВЕ1Г. тахт += тахт Когда парсер ЯАХ встречает текст, он вызывает метод ха1,вах.лапо1ег. СоптептНап01ег. спагастегв(). Нет никакой гарантии, что этот метод будет вызван один раз для всего текста — текст может передаваться частями.
По этой причине метод просто накапливает текст, а запись текста в словарь выполняется, только когда будет встречен соответствующий закрывающий тег. (Более эффективно было бы сделать переменную ве1Г. техт списком тело этого метода — вызовом метода ве1(. техт, аррепб(техт) и внести соответствующие изменения в другие методы.) Реализация с использованием ЯАХ АР1 существенно отличается от реализации с использованием дерева элементов или 1)ОМ, но она намного эффективнее. Мы можем реализовать другие обработчики и переопределить другие методы в обработчике содержимого, чтобы получить более полный контроль над процессом парсинга.
Парсер ЯАХ не поддерживает возможность создания представления документа ХМЬ, что делает его идеальным инструментом для чтения данных в формате ХМЬ в наши собственные коллекции, но это также означает, что при использовании ЯАХ в памяти нет никакого»документа», готового к записи в файл в формате ХМЬ, поэтому запись должна выполняться с использованием одного из подходов, рассматривавшихся выше в этом разделе. Произвольный доступ к двоичным данным в файлах В предыдущих разделах рассматривалась методика, когда все данные программы целиком читаются в память, обрабатываются и затем целиком записываются в файл. В современных компьютерах так много оперативной памяти, что эта методика имеет полное право на существование даже в случае больших объемов данных. Однако в некоторых ситуациях более предпочтительной может оказаться методика, когда данные полностью хранятся на диске, в память небольшими порциями читаются только необходимые данные, а на диск записываются только изменения.
Подход, основанный на произвольном доступе к данным на Произвольный доступ к двоичным данным в файлах 377 диске, легко реализовать при использовании базы данных типа ключ- значение («1)ВМ») или полноценной базы данных ЯЯ! — оба варианта будут рассматриваться в главе 11, а в этом разделе будет показано, как вручную реализовать произвольный доступ к данным в файлах. Для начала будет представлен класс В1пагуйесогСЕ1)е. В!пагуйесогСЕз!е. Экземпляры этого класса являются универсальным представлением двоичных файлов, доступных для чтения и записи, состоящих из последовательности записей фиксированной длины. Затем, чтобы продемонстрировать, как использовать двоичные файлы с произвольным доступом, будет рассмотрен класс ВзкеЯтоск.