Как лексировать, анализировать и сериализовать сообщения электронной почты в XML с помощью Alex и Happy

Я работаю над тем, чтобы иметь возможность вводить любое сообщение электронной почты и выводить эквивалентную кодировку XML.

Я начинаю с малого, с одного из заголовков электронной почты — «Из заголовка».

Вот пример заголовка From:

From: John Doe <[email protected]>

Я хочу, чтобы он был преобразован в этот XML:

<From>
    <Mailbox>
        <DisplayName>John Doe</DisplayName>
        <Address>[email protected]</Address>
    </Mailbox>
</From>

Я хочу использовать лексический анализатор "Алекс" (http://www.haskell.org/alex/doc/html/), чтобы разбить (токенизировать) заголовок From.

Я хочу использовать парсер Happy (http://www.haskell.org/happy/ ) для обработки токенов и создания дерева синтаксического анализа.

Затем я хочу использовать сериализатор для обхода дерева синтаксического анализа и вывода XML.

Формат заголовка From определяется форматом сообщений Интернета (IMF), RFC 5322 (http://tools.ietf.org/html/rfc5322).

Вот еще несколько примеров From Headers и желаемого вывода XML:

Из заголовка без отображаемого имени:

From: <[email protected]>

Желаемый вывод XML:

<From>
    <Mailbox>
        <Address>[email protected]</Address>
    </Mailbox>
</From>

Из заголовка без отображаемого имени и без угловых скобок вокруг адреса:

From: [email protected]

Желаемый вывод XML:

<From>
    <Mailbox>
        <Address>[email protected]</Address>
    </Mailbox>
</From>

Из заголовка с несколькими почтовыми ящиками, разделенными запятой:

From: <[email protected]>, "Simon St. John" <[email protected]>, [email protected]

Желаемый вывод XML:

<From>
    <Mailbox>
        <Address>[email protected]</Address>
    </Mailbox>
    <Mailbox>
        <DisplayName>Simon St. John</DisplayName>
        <Address>[email protected]</Address>
    </Mailbox>
    <Mailbox>
        <Address>[email protected]</Address>
    </Mailbox>
</From>

В RFC 5322 говорится, что синтаксис комментария следующий: (…). Вот заголовок From, содержащий комментарий:

From: (this is a comment) "John Doe" <[email protected]>

Я хочу, чтобы все комментарии удалялись во время лексирования.

Желаемый вывод XML таков:

<From>
    <Mailbox>
        <DisplayName>John Doe</DisplayName>
        <Address>[email protected]</Address>
    </Mailbox>
</From>

В RFC говорится, что в заголовке From могут быть разбросаны «складывающиеся пробелы». Вот заголовок From с токеном From: в первой строке, отображаемым именем во второй строке и адресом в третьей строке:

From: 
    "John Doe" 
    <[email protected]>

На вывод XML не должны влиять сворачивающиеся пробелы:

<From>
    <Mailbox>
        <DisplayName>John Doe</DisplayName>
        <Address>[email protected]</Address>
    </Mailbox>
</From>

В RFC сказано, что после символа @ в адресе может быть строка, заключенная в скобки, например такая:

From: "John Doe" <john@[website]>

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

<From>
    <Mailbox>
        <DisplayName>John Doe</DisplayName>
        <Address>john@[website]</Address>
    </Mailbox>
</From>

Обработка ошибок

Я хочу, чтобы ошибка генерировалась, если заголовок From неверен. Вот пара примеров ошибочных заголовков From и желаемого результата:

Отображаемое имя ошибочно помещено после адреса:

From: <[email protected]> "John Doe"

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

serialize: parse error at line 1 and column 22. Error occurred at "John Doe"

В этом заголовке From есть ошибочная цифра "23" перед отображаемым именем:

From: 23 "John Doe" <[email protected]>

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

serialize: parse error at line 1 and column 10. Error occurred at "John Doe"

Не могли бы вы показать, как реализовать лексер, парсер и сериализатор?


person Roger Costello    schedule 27.06.2013    source источник


Ответы (1)


Разделите задачу на пять шагов:

Шаг 1: укажите полный официальный BNF для заголовка From.

Шаг 2: создайте функцию лексического анализа lex, которая разбивает заголовок From на последовательность небольших фрагментов, таких как from:, displayName, angleAddress и т. д. Эти небольшие фрагменты называются токенами.

lex :: String -> [Token]

Шаг 3: определите тип данных From для представления заголовка From.

Шаг 4: создайте функцию синтаксического анализатора parser, которая использует последовательность токенов и создает дерево синтаксического анализа типа From.

parse :: [Token] -> From

Шаг 5: создайте функцию serialize, которая проходит по дереву синтаксического анализа и генерирует XML.

serialize :: From -> XML

Шаг № 1: укажите полный авторитетный BNF для формата данных

Полный официальный BNF для заголовка From указан в RFC 5322. Я извлек части, применимые к заголовку From:

http://www.xfront.com/parsing/RFC-5322/From-Header/From-Header-BNF.pdf

Шаг № 2: создайте лексер, который разбивает заголовки From на токены

Вот пример, показывающий, как будут токенизированы заголовки From:

Токенизировать этот заголовок From:

From: "John Doe" <[email protected]>

Результатом лексера является этот список токенов:

[ 
  TokenFrom (AlexPn 0 1 1)
  , TokenDisplayName (AlexPn 6 1 7) "\"John Doe\""
  , TokenAngleAddress (AlexPn 17 1 18) "<[email protected]>"
]

Каждый элемент в списке состоит из метки токена, информации о позиции и, возможно, значения. Информация о позиции указана в скобках. «AlexPn» — это метка, указывающая, что это информация о местоположении. Следующие три числа указывают местоположение маркера: начальное местоположение, номер строки и номер столбца.

Ниже приведен лексер для BFN. Обратите внимание на однозначное соответствие между определениями BNF и токенов. Например, в БНФ есть такое производственное правило:

qcontent  = ( qtext  |  quoted-pair )

Лексер имеет следующее определение токена:

@qcontent = ( $qtext | @quoted_pair )

Если не считать незначительных синтаксических различий, они идентичны. Это действительно мощно. Предполагая, что определение письма «Из заголовка» правильное (т. е. BNF правильный), мы можем быть уверены, что лексер будет правильным.

Вот лексер:

http://www.xfront.com/parsing/RFC-5322/From-Header/Lexer.x.txt

Шаг № 3: определите тип данных для представления заголовка From

Последовательность токенов будет внутренне представлена ​​с использованием этого типа данных:

data From
    = From MailboxList
    deriving Show

type MailboxList
    = [ Mailbox ]

data Mailbox
    = LongMailbox DisplayName AngleAddress
    | AngleMailbox AngleAddress
    | ShortMailbox AddressSpecification
    deriving Show

data DisplayName
    = DisplayName String
    deriving Show

data AngleAddress
    = AngleAddress String
    deriving Show

data AddressSpecification
    = AddressSpecification String
    deriving Show

Шаг № 4: создайте синтаксический анализатор — используйте последовательность токенов и создайте дерево синтаксического анализа типа «От».

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

Разберите это Из заголовка:

From: "John Doe" <[email protected]>

Результат работы синтаксического анализатора — это дерево синтаксического анализа:

From 
    [
        LongMailbox 
            (DisplayName "John Doe") 
            (AngleAddress "[email protected]")
    ]

Вот парсер:

http://www.xfront.com/parsing/RFC-5322/From-Header/Parser.y.txt

Шаг № 5: пройдитесь по дереву синтаксического анализа и добавьте пары начальных и конечных тегов XML вокруг значений

Для каждой грамматики есть функция. Например, вот функция для создания грамматики From:

serialize :: From -> String
serialize (From mailboxList) = "<From>" ++ serializeMailboxList mailboxList ++ "</From>"

Аргументом функции является корень дерева синтаксического анализа с меткой From. Функция вызывает другую функцию, serializeMailboxList, для обработки дочерних элементов корня. Результат помещается в пары От начального тега до конечного тега.

Вот сериализатор XML:

http://www.xfront.com/parsing/RFC-5322/From-Header/serialize.hs.txt

person Roger Costello    schedule 27.06.2013