Лексер для XML — документа-регулярное выражение для данных XML-элементов скрывает регулярное выражение для пробелов-как это исправить?

#bison #flex-lexer #yacc #lex

Вопрос:

Я создаю лексер для XML — документа. Вот мой XML-документ (обратите внимание, что фактический XML-документ намного сложнее, это простой XML-документ, показывающий проблему):

 lt;?xml version="1.0" encoding="UTF-8"?gt; lt;Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:noNamespaceSchemaLocation="test.xsd"  version="1.0"gt;  lt;messagegt;Hello, worldlt;/messagegt; lt;/Documentgt;  

Я хочу, чтобы лексер произвел это:

 DOCUMENT_START_TAG ATTRIBUTE_NAME = version ATTRIBUTE_VALUE = "1.0" MESSAGE_START_TAG ELEMENT_VALUE = Hello, world MESSAGE_END_TAG DOCUMENT_END_TAG  

То есть я хочу, чтобы лексер игнорировал первую строку (объявление XML), пробелы между элементами и два объявления пространства имен.

Но вместо этого лексер производит это:

 ELEMENT_VALUE =  DOCUMENT_START_TAG ATTRIBUTE_NAME = version ATTRIBUTE_VALUE = "1.0" ELEMENT_VALUE =  MESSAGE_START_TAG ELEMENT_VALUE = Hello, world MESSAGE_END_TAG ELEMENT_VALUE =  DOCUMENT_END_TAG  

Правило лексера для пробелов не срабатывает. Вместо этого правило для значения элемента срабатывает. Поэтому я знаю, в чем проблема: регулярное выражение для значения элемента неверно. Но я не знаю, что такое правильное регулярное выражение. Любая помощь, которую вы могли бы оказать, будет очень признательна.

Внизу находится весь мой файл .l. Вот объяснение содержащихся в нем правил:

Первая строка-строка объявления XML-это то, что я хочу, чтобы лексер просто отбросил. Вот правило лексера для этого:

 "lt;?"[^?gt;] "?gt;"  

XML — декларация начинается с lt;? и заканчивается ?gt; , и все, что между ними, — это что угодно, кроме ? и gt;

Я хочу, чтобы лексер удалял пробелы между элементами XML. Вот правило лексера для пробелов:

 [ tn]   

That gobbles up spaces, tabs, and newlines.

I want the lexer to ignore the two namespace declarations. Here are the lexer rules for them:

 [ tn] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" [ tn] xsi:noNamespaceSchemaLocation="test.xsd"  

Namespace declarations are always preceded by at least one whitespace character.

I want the lexer to return the token DOCUMENT_START_TAG for the lt;Documentgt; element. The lt;Documentgt; element has attributes bundled inside of it, so that requires some special care:

 "lt;Document"[^gt;]*"gt;" { yyless(9); return(DOCUMENT_START_TAG); }  

The lt;Documentgt; element starts with lt;Document and then there is some stuff and then it ends with gt; . The action puts back everything following lt;Document and returns the token DOCUMENT_START_TAG .

I want the lexer to return DOCUMENT_END_TAG for lt;/Documentgt; . Here’s the lexer rule:

 "lt;/Documentgt;" { return(DOCUMENT_END_TAG); }  

Here are the lexer rules for the message start and end tags:

 "lt;messagegt;" { return(MESSAGE_START_TAG); } "lt;/messagegt;" { return(MESSAGE_END_TAG); }  

An XML attribute has a name, equals sign, and value wrapped in quotes. Here is the lexer rule for the name:

[^ tn=] /=[ tn]*» { return(ATTRIBUTE_NAME); }

The name doesn’t contain space, tab, newline, or equals sign. (Using the lookahead operator) following a name is an equals sign, possibly some whitespace, and a quote.

The attribute value is the stuff within quotes:

 "[^"]*" { return(ATTRIBUTE_VALUE); }  

Я не хочу, чтобы значение атрибута содержало кавычки — как их удалить?

Я хочу, чтобы лексер возвращал значение элементов (например, Привет, мир). Значение элемента не содержит lt; или gt;

 [^lt;gt;] /lt; { return(ELEMENT_VALUE); }  

Я использую lookahead, чтобы указать, что за значением всегда следует lt;

Вот мой полный файл .l:

 %{  enum yytokentype {  DOCUMENT_START_TAG = 258,  DOCUMENT_END_TAG = 259,  MESSAGE_START_TAG = 260,  MESSAGE_END_TAG = 261,  ELEMENT_VALUE = 262,  ATTRIBUTE_NAME = 263,  ATTRIBUTE_VALUE = 264,  JUNK = 265  };  int yyval; %} %% "lt;?"[^?gt;] "?gt;" [ tn]  "gt;" "=" [ tn] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" [ tn] xsi:noNamespaceSchemaLocation="test.xsd" "lt;Document"[^gt;]*"gt;" { yyless(9); return(DOCUMENT_START_TAG); } "lt;/Documentgt;" { return(DOCUMENT_END_TAG); } "lt;messagegt;" { return(MESSAGE_START_TAG); } "lt;/messagegt;" { return(MESSAGE_END_TAG); } [^ tn=] /=[ tn]*" { return(ATTRIBUTE_NAME); } "[^"]*" { return(ATTRIBUTE_VALUE); } [^lt;gt;] /lt; { return(ELEMENT_VALUE); } . { return(JUNK); } %%  int yywrap(){ return 1;} int main(int argc, char *argv[]) {  yyin = fopen(argv[1], "r");  int tok;  while (tok = yylex()) {  switch (tok){  case 258:  printf("DOCUMENT_START_TAGn");  break;  case 259:  printf("DOCUMENT_END_TAGn");  break;  case 260:  printf("MESSAGE_START_TAGn");  break;  case 261:  printf("MESSAGE_END_TAGn");  break;  case 262:  printf("ELEMENT_VALUE = %sn", yytext);  break;  case 263:  printf("ATTRIBUTE_NAME = %sn", yytext);  break;  case 264:  printf("ATTRIBUTE_VALUE = %sn", yytext);  break;  case 265:  printf("JUNK = %sn", yytext);  break;  default:  printf(" = invalid token, value = %sn", yytext);  }  }    fclose(yyin);    return 0; }  

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

1. Кроме того, пожалуйста, обратите внимание, что правило lex должно иметь действие . Иногда это вообще не работает, но это неопределенное поведение. Если вы хотите написать правило, которое ничего не делает, используйте ; или {} в качестве действия. Никаких дополнительных затрат не требуется. 🙂

2. Ах! Я этого не понимал. Спасибо @rici

Ответ №1:

Ваше правило для значения элемента всегда выигрывает над вашим правилом пробелов, потому что оно имеет более длинное соответствие. Это связано с тем, что конечный контекст считается частью совпадения, даже если лексер возвращается в конечный контекст перед запуском действия.

Это задокументировано в руководстве по Flex, но его легко пропустить.

Мне непонятно, почему вы чувствуете необходимость в завершающем контексте. Единственными символами, которые могут следовать [^lt;gt;] , являются lt; и gt; ; если вы хотите рассматривать gt; как ошибку, было бы более разумно отметить ошибку в точке, где gt; она возникает, чем отмечать ее в начале значения элемента, которое в конечном итоге содержит символ-нарушитель. Но, вероятно, еще более разумно просто спокойно воспринимать gt; его как обычного персонажа. В любом случае, конечный контекст не требуется, и без этого конечного контекста ваше правило пробелов выиграет, если это применимо.

Но обратите внимание, что если в XML-документе использовались окончания строк CRLF, правило пробелов не поймает их. Я всегда предлагаю использовать [[:space:]] вместо перечисления пробелы, хотя они совпадают с некоторыми символами, которые могут считаться ошибками.

Аналогично, сканирование тега до закрытия gt; , а затем возврат к тегу совершенно бессмысленно. Либо тег правильно завершен , и вы в конечном итоге достигнете gt; , либо вы попадете в конец документа, и в этот момент вы можете выдать ошибку. Однако вам следует перехватывать теги , имена которых начинаются с Document , например lt;Documentarygt; (которые будет принимать ваш текущий шаблон). Это наводило бы на мысль о чем-то вроде:

 lt;Document { return DOCUMENT_START_TAG; } lt;message { return MESSAGE_START_TAG; } lt;/Document { return DOCUMENT_END_TAG; } lt;/message { return MESSAGE_END_TAG; } lt;/[^[:space:]gt;]  { return UNKNOWN_END_TAG; } lt;[^[:space:]gt;]  { return UNKNOWN_START_TAG; }