Главная » XSLT » Обработка неструктурированного текста с помощью регулярных выражений

0

Задача

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

Решение

Для работы с регулярными выражениями в XSLT 2.0 есть три функции: match(), replace() и tokenize(). Мы рассматривали их в главе 1. Появилась также новая команда xsl:analyze-string(), которая позволяет обрабатывать текст еще более интересными способами.

У команды xsl:analyze-string() имеется атрибут select для задания подлежащей обработке строки, атрибут regex для задания применяемого к стро­ке регулярного выражения и необязательный атрибут flags для более точного указания того, как должно применяться регулярное выражение. Ниже перечисле­ны стандартные флаги:

?      i – режим учета регистра;

?      m – многострочный режим, в котором метасимволы А и $ сопоставляются с началом и концом линейных строк (lines), а не строки (string) в целом (режим по умолчанию);

?      s – метасимвол . сопоставляется с символами новой строки (
). По умолчанию это не так. Иногда этот режим называют однострочным, но из определения должно быть ясно, что это не противоположность много­строчному режиму; флаги m и s можно задавать совместно;

?      x – в регулярном выражении допускается в качестве разделителей исполь­зовать пробелы, которые перестают быть значащими символами.

Для обработки подстроки, сопоставившейся с регулярным выражением, применяется дочерний элемент xsl:matching-substring, а для обработ­ки несопоставившейся строки – элемент xsl:non-matching-substring. Любой из них может быть опущен. Можно также ссылаться на запомненные группы (части регулярного выражения, заключенные в круглые скобки), для чего служит функция regex-group, вызываемая внутри xsl:matching- substring:

<xsl:template match="date"> <xsl:copy>

<xsl:analyze-string select="normalize-space(.)"

regex="(\d\d\d\d) ( / | – ) (\d\d) ( / | – ) (\d\d)" flags="x"> <xsl:matching-substring>

<year><xsl:value-of select="regex-group(1)"/></year> <month><xsl:value-of select="regex-group(3)"/></month> <day><xsl:value-of select="regex-group(5)"/></day> </xsl:matching-substring> <xsl:non-matching-substring>

<error><xsl:value-of select="."/></error> </xsl:non-matching-substring> </xsl:analyze-string> </xsl:copy> </xsl:template>

Полезным дополнением к xsl:analyze-string() является функция unparsed-text(). Она позволяет считывать содержимое текстового файла как строку. Следовательно, файл не анализируется и потому не обязательно должен быть представлен в формате XML. Вообще, использовать эту функцию для чте­ния XML-файлов имеет смысл разве что в исключительных случаях.

Следующая таблица стилей преобразовывает простой файл с полями, разде­ленными запятыми, (без заключенных в кавычки строк) в формат XML:

<xsl:stylesheet version="2.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2 0 01/XMLSchema" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes">

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="csv-file" select=" ‘test.csv’ "/>

<xsl:template match="/">

<converted-csv filename="{$csv-file}">

<xsl:for-each select="tokenize(unparsed-text($csv-file, ‘UTF-8′),

‘\n’)">

<xsl:if test="normalize-space(.)"> <row>

<xsl:analyze-string select="." regex="," flags="x"> <xsl:non-matching-substring>

<col><xsl:value-of select="normalize-space(.)"/></col>

</xsl:non-matching-substring> </xsl:analyze-string> </row> </xsl:if> </xsl:for-each> </converted-csv>

</xsl:template>

</xsl:stylesheet>

Обсуждение

Добавленный в XSLT 2.0 механизм регулярных выражений вкупе с функцией unparsed-text() открывает целый ряд новых возможностей, которые были практически недоступны в XSLT 1.0. Тем не менее, я стал бы использовать XSLT для обработки документов, представленных не в формате XML, только в том слу­чае, когда мультиязычное решение (например, Java и XSLT или Perl и XSLT) по какой-то причине неприемлемо. Разумеется, если XSLT – единственный язык, ко­торым вы хотите владеть в совершенстве, то новые средства – непаханое поле для экспериментов.

Отчасти мое стремление сойти с корабля XSLT при входе в область обработки неструктурированного текста связано с некоторыми чертами, которых недостает команде xsl:analyze-string. Хотелось бы, чтобы функции position() и last() работали внутри элемента xsl:matching-string и сообщали, что данное совпадение имеет номер position() из last() найденных. Иногда я исполь­зую цикл xsl:for-each по результатам, возвращенным tokenize(), вместо xsl:analyze-string, но и это решение не идеально, поскольку возвращают­ся только не сопоставившиеся части. Но часто для сложного анализа, где число возможных сопоставлений с регулярным выражением, содержащим альтерна­тивы ( | ), велико, не существует никакого другого выхода, кроме как воспользо­ваться xsl:analyze-string. Однако невозможно сказать, с каким регуляр­ным выражением произошло сопоставление без повторного опроса с помощью функции match(), а на мой вкус это избыточно и расточительно, поскольку сам-то механизм regex наверняка знает, с какой частью строки только что про­изошло сопоставление:

<xsl:template match="text()">

<xsl:analyze-string select="."

regex=’ [\-+]?\d\.\d+\s*[eE][\-+]?\d+                         |

[\- + ]?\d+\.\d+ |

[\- + ]?\d+ | "[A"]*?"                                       

flags="x"> <xsl:matching-substring>

<xsl:choose>

<xsl:when test="matches(.,'[\-+]?\d\.\d+\s*[eE][\-+]?\d+’)"> <scientific><xsl:value-of select="."/></scientific> </xsl:when>

<xsl:when test="matches(.,'[\-+]?\d+\.\d+’)"> <decimal><xsl:value-of select="."/> </decimal> </xsl:when>

<xsl:when test="matches(.,'[\-+]?\d+’)">

<integer><xsl:value-of select="."/> </integer> </xsl:when>

<xsl:when test=’matches(.," "" [л""]*? "" ", "x")’>

<string><xsl:value-of select="."/></string> </xsl:when> </xsl:choose> </xsl:matching-substring> </xsl:analyze-string> </xsl:template>

Впрочем, критиковать всякий горазд. Разумеется, когда совершенствуешь язык, приходится преодолевать самые разнообразные проблемы и идти на компромиссы. Тем не менее, при всем уважении к комитету, работавшему над спецификацией XSLT 2.0, я хотел бы, чтобы команда xsl:analyze-string работала следующим образом:

<!— ЭТО НЕ КОРРЕКТНЫЙ XSLT 2.0, а лишь мечты авторы —>

<xsl:template match="text()"> <xsl:analyze-string select="."

flags="x">

<xsl:matching-substring regex="[\-+]?\d\.\d+\s*[eE][\-+]?\d+">

<scientific><xsl:value-of select="."/></scientific> </xsl:matching-substring>

<xsl:matching-substring regex="[\-+]?\d+\.\d+’"> <decimal><xsl:value-of select="."/> </decimal> </xsl:matching-substring>

<xsl:matching-substring regex=" [\-+]?\d+’)">

<integer><xsl:value-of select="."/> </integer> </xsl:matching-substring>

<xsl:matching-substring regex=’ "[*"]*?" >

<string><xsl:value-of select="."/></string> </xsl:matching-substring> <xsl:non=matching-substring>

<other><xsl:value-of select="."/></other> </xsl:non=matching-substring> </xsl:analyze-string> </xsl:template>

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии