Простая грамматика ParseKit для HTML с заменой переменных - PullRequest
5 голосов
/ 15 февраля 2012

Для приложения iOS я хочу проанализировать HTML-файл, который может содержать переменные стиля UNIX для замены. Например, HTML может выглядеть так:

<html>
  <head></head>
  <body>
    <h1>${title}</h1>
    <p>${paragraph1}</p>
    <img src="${image}" />
  </body>
</html>

Я пытаюсь создать простую грамматику ParseKit, которая предоставит мне два обратных вызова: один для сквозного HTML, а другой для переменных, которые он обнаруживает. Для этого я создал следующую грамматику:

@start        = Empty | content*;

content       = variable | passThrough;
passThrough   = /[^$]+/;
variable      = '$' '{' Word closeChar;

openChar      = '${';
closeChar     = '}';

Я сталкиваюсь как минимум с двумя проблемами: для variable я изначально объявил его как openChar Word closeChar, но он не работал (я до сих пор не знаю, почему). Вторая проблема (и более важная) заключается в том, что анализатор останавливается, когда находит <img src"${image}" /> (т.е. переменную внутри строки в кавычках).

Мои вопросы:

  1. Как я могу изменить грамматику, чтобы она работала как положено?
  2. Лучше использовать токенизатор? Если это так, как мне его настроить?

1 Ответ

4 голосов
/ 16 февраля 2012

Разработчик ParseKit здесь. Я отвечу на оба ваших вопроса:

1) Вы выбираете правильный подход, но это сложный случай. Есть несколько небольших ошибок, и ваша грамматика должна быть немного изменена.

Я разработал грамматику, которая работает для меня:

// Tokenizer Directives
@symbolState = '"' "'"; // effectively tells the tokenizer to turn off QuoteState. 
                      // Otherwise, variables enclosed in quotes would not be found (they'd be embedded in quoted strings). 
                      // now single- & double-quotes will be recognized as individual symbols, not start- & end-markers for quoted strings

@symbols = '${'; // declare '${' as a multi-char symbol

@reportsWhitespaceTokens = YES; // tell the tokenizer to preserve/report whitespace

// Grammar
@start = content*;
content = passthru | variable;
passthru = /[^$].*/;
variable = start name end;
start = '${';
end = '}';
name = Word;

Затем реализуйте эти два обратных вызова в вашем ассемблере:

- (void)parser:(PKParser *)p didMatchName:(PKAssembly *)a {
    NSLog(@"%s %@", __PRETTY_FUNCTION__, a);
    PKToken *tok = [a pop];

    NSString *name = tok.stringValue;
    // do something with name
}

- (void)parser:(PKParser *)p didMatchPassthru:(PKAssembly *)a {
    NSLog(@"%s %@", __PRETTY_FUNCTION__, a);
    PKToken *tok = [a pop];

    NSMutableString *s = a.target;
    if (!s) {
        s = [NSMutableString string];
    }

    [s appendString:tok.stringValue];

    a.target = s;
}

И тогда ваш код клиента / драйвера будет выглядеть примерно так:

NSString *g = // fetch grammar
PKParser *p = [[PKParserFactory factory] parserFromGrammar:g assembler:self];
NSString *s = @"<img src=\"${image}\" />";
[p parse:s];
NSString *result = [p parse:s];
NSLog(@"result %@", result);

Будет напечатано:

result: <img src="" />

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

PKTokenizer *t = [PKTokenizer tokenizerWithString:s];
[t setTokenizerState:t.symbolState from:'"' to:'"'];
[t setTokenizerState:t.symbolState from:'\'' to:'\''];
[t.symbolState add:@"${"];
t.whitespaceState.reportsWhitespaceTokens = YES;

NSMutableString *result = [NSMutableString string];

PKToken *eof = [PKToken EOFToken];
PKToken *tok = nil;
while (eof != (tok = [t nextToken])) {
    if ([@"${" isEqualToString:tok.stringValue]) {
        tok = [t nextToken];
        NSString *varName = tok.stringValue;

        // do something with variable
    } else if ([@"}" isEqualToString:tok.stringValue]) {
        // do nothing
    } else {
        [result appendString:tok.stringValue];
    }
}
...