Разбор сбалансированных вложенных шаблонов вики и извлечение содержимого параметра одной строки с помощью регулярного выражения

Я знаю, что анализ вложенных строк или HTML лучше выполнять с помощью реального синтаксического анализатора, но в моем случае у меня были простые шаблоны, и я хотел извлечь содержимое заголовка параметра Wiki "title" из шаблона. Мне потребовалось некоторое время, чтобы достичь этого, но благодаря инструменту регулярных выражений Ларса Олава Торвика ( http://regex.larsolavtorvik.com/) и этому форуму пользователей, здесь я дошел до этого. Может быть, кто-то найдет это полезным. (Мы все хотим внести свой вклад, он, не так ли?;-) Следующий код с комментариями делает свое дело. Я должен был сделать это с осмотром утверждений, чтобы смешать не два шаблона, если в одном из них нет заголовка.

Я еще не уверен в двух вопросах в комментариях к регулярным выражениям - см. (?# Questions: …)- если я понял рекурсивную часть в (?R), Является ли это, что он получает свой контент для проверки с самого внешнего определенного уровня, то есть второй строки регулярного выражения \{\{ и последняя строка регулярного выражения \}\}? Это было бы правильно? И в чем разница между ++ а также + перед альтернативой (?R) Бут работает одинаково, так кажется при тестировании.

  1. Оригинальные вики-шаблоны на странице (самые простые):

    $wikiTemplate = "
    {{Templ1
    | title = (1. template) title
    }}
    
    {{Templ2
    | any parameter = something {{template}}
    }}
    
    {{Templ1
    | title = (3. template) title
    }}
    ";
    
  2. Замена:

    $wikiTemplate = preg_replace(
      array(
      // tag all templates with START … END and add a TITLE-placeholder before
      // and take care of balanced {{ …  }} recursiveness 
        "@(?s)   (?# switch to dotall match, i.e. also linebreaks )
          \{\{ (?# find two {{ )
          (?: (?# group 1 as a non-backreferenced match  )
            (?:  (?# group 2 as a non-backreferenced match  )
              (?! (?# in group 1 anything but not {{ or }} )
                \{\{ 
                |   (?# or )
                \}\}
              )
              .
            )++  (?# Question: what is the differenc between ++ and + here? )
            |    (?# or )
            (?R) (?# is it recursive of what is defined in the outermost,
                  i.e. 2nd regexp line with \{\{ and last line with \}\}
                  Question: is that here understood correctly? ) 
          )
          * (?# zero or many times of the inner regexp defintions )
          \}\} (?# find two }} )
        @x",// x-extended → ignore white space in the pattern
      // replace TITLE by single line content of title parameter 
        "@
          (?<=TITLE) (?# TITLE must preceed the following linebreak but is not
                      backreferenced within \\0, i.e. the whole returned match)
          ([\n\r]+)  (?#linebr in 1 may also described as . because of
                      s-modifier dotall)
          (?:        (?# start non-backreferenced match )
            .        (?# any character but not followed by START)
            (?!START)
          )+      (?# multiple times)
          (?:     (?# start non-backreferenced match )
            \|\s*title\s*=\s* (?#find the parameter '| title = ')
          )
          ([^\r\n]+)  (?#get title now to \\2 but exclude the line break. 
                       Note it is buggy when there is no line break )
          (?:     (?# start non-backreferenced match )
            .     (?# any character but not followed by END)
            (?!END)
          )
          +       (?# multiple times)
          .       (?# any single character, e.g. the last  because as all
                   stuff before captures anything not followed by END)
          (?:END) (?#a not backreferenced END)
        @msx", // m-multiline, s-dotall match also linebreaks,
               // x-extended → ignore white space in the pattern
      ), 
      array(
        "TITLE\nSTART\\0END", // \0 is the whole returned match, i.e. the template
      # replace the TITLE to  TITLEtitle contentTITLE…
        "\\2TITLE\\0",
      ),
      $wikiTemplate
    );
    print_r($wikiTemplate);
    
  3. В результате получается заголовок, помеченный TITLE над каждым шаблоном, но только если заголовок был:

    TITLE(1. template) titleTITLE
    START{{Templ1
     | title = (1. template) title
    }}END
    
    TITLE
    START{{Templ2
     | any parameter = something {{template}}
    }}END
    
    TITLE(3. template) titleTITLE
    START{{Templ1
     | title = (3. template) title
    }}END
    

Есть ли у меня вопросы относительно понимания регулярных выражений или некоторых улучшений? Спасибо, Андреас.

1 ответ

++ является собственническим квантификатором. Если вы добавите квантификатор повторения (+, *, {...}) с + это становится притяжательным. Это означает, что механизм регулярных выражений не будет возвращаться назад и пробовать меньше повторений, как только он покинет повторение в первый раз. Таким образом, они в основном делают повторение атомной группой. Иногда это оптимизация, а иногда это действительно имеет значение. Вы можете сделать очень хорошее чтение здесь.

А про твой второй вопрос да (?R) просто попытаюсь снова соответствовать полному шаблону. Для этого есть хорошая статья в документации PHP PCRE.

Если у вас есть другие вопросы, лучше задать их на Code Review.

Другие вопросы по тегам