PHP正则表达式捕获模板标签和if语句标签

时间:2018-01-15 23:04:25

标签: php regex

我在这样的html文件中有标记,放在一起;

*|SUBJECT|*
*|SUBJECT|*
*|IFNOT:ARCHIVE_PAGE|*
    *|ARCHIVE|*
*|END:IF|*
*|FACEBOOK:PROFILEURL|*
*|TWITTER:PROFILEURL|*
*|FORWARD|*
*|IF:REWARDS*
    *|REWARDS|*
*|END:IF|*

使用这个PHP函数和正则表达式我可以得到所有标签的结果

preg_match_all("/\*\|(.*?)\|\*/", $this->template, $elements);
    $this->elements["Tags"] = $elements[0];
    $this->elements["TagNames"] = $elements[1];

我想要的是找到一种方法来捕获IF:(TAG)语句和IFNOT:(TAG)语句以及内容。

到目前为止我所拥有的是

ergex=> /\*\|IF(([A-Z{0-3}]):([A-Z_]+))\|\*(.*?)\*\|END:IF\|\*|\*\|(.*?)\|\*/g

但它只是将自己的标签作为一个整体捕获,任何人都可以指出我正确的方向或帮助我。

1 个答案:

答案 0 :(得分:2)

正如我在评论中提到的,你的方法是简单化,我可以让你开始使用我用于这些事情的方法。它更像是一个tokenizer / lexer / parser方法。

这听起来很大而且可怕但实际上却让它更简单

<?php
function parse($subject, $tokens)
{
    $types = array_keys($tokens);
    $patterns = [];
    $lexer_stream = [];
    $result = false;
    foreach ($tokens as $k=>$v){
        $patterns[] = "(?P<$k>$v)";
    }
    $pattern = "/".implode('|', $patterns)."/i";
    if (preg_match_all($pattern, $subject, $matches, PREG_OFFSET_CAPTURE)) {
        //print_r($matches);
        foreach ($matches[0] as $key => $value) {
            $match = [];
            foreach ($types as $type) {
                $match = $matches[$type][$key];
                if (is_array($match) && $match[1] != -1) {
                    break;
                }
            }
            $tok  = [
                'content' => $match[0],
                'type' => $type,
                'offset' => $match[1]
            ];
            $lexer_stream[] = $tok;
        }
        $result = parseTokens( $lexer_stream );
    }
    return $result;
}

function parseTokens( array &$lexer_stream ){
    $result = [];
    $mode = 'none';
    while($current = current($lexer_stream)){
        $content = $current['content'];
        $type = $current['type'];
        switch($type){
            case 'T_WHITESPACE':
                next($lexer_stream);
            break;
            case 'T_TAG_START': 
                $mode = 'start';
                next($lexer_stream);
            break;
            case 'T_WORD': 
                if($mode == 'start') echo "Tag $content\n";
                if($mode == 'ifnot') echo "IfNot $content\n";
                next($lexer_stream);
            break;
            case 'T_TAG_END': 
                $mode = 'none';
                next($lexer_stream);
            break;
            case 'T_IFNOT':
                $mode = 'ifnot';
                next($lexer_stream);
            break;

            case 'T_EOF': return;

            case 'T_UNKNOWN':
            default:
                print_r($current);
                trigger_error("Unknown token $type value $content", E_USER_ERROR);
        }
    }
    if( !$current ) return;
    print_r($current);
    trigger_error("Unclosed item $mode for $type value $content", E_USER_ERROR);
}

$subject = '*|SUBJECT|*
*|SUBJECT|*
*|IFNOT:ARCHIVE_PAGE|*
    *|ARCHIVE|*
*|END:IF|*
*|FACEBOOK:PROFILEURL|*
*|TWITTER:PROFILEURL|*
*|FORWARD|*
*|IF:REWARDS*
    *|REWARDS|*
*|END:IF|*';

$tokens = [
    'T_WHITESPACE'      => '[\r\n\s\t]+',
    'T_TAG_START'       => '\*\|',
    'T_TAG_END'         => '\|\*',
    'T_IF'              => 'IF:',
    'T_IFNOT'           => 'IFNOT:',    
    'T_ENDIF'           => 'END:IF',
    'T_WORD'            => '\w+',
    'T_EOF'             => '\Z',
    'T_UNKNOWN'         => '.+?'
];

parse($subject,$tokens);

所以你可以看到here

它输出:

Tag SUBJECT
Tag SUBJECT
IfNot ARCHIVE_PAGE
Tag ARCHIVE
Array
(
    [content] => END:IF
    [type] => T_ENDIF
    [offset] => 69
)
<br />
<b>Fatal error</b>:  Unknown token T_ENDIF value END:IF in <b>[...][...]</b> on line <b>67</b><br />

错误是因为我只对End if标签进行了处理(必须留下一些东西供您使用)。

对于我用另一个问题执行的解析器,你可以在我的github上找到它

https://github.com/ArtisticPhoenix/MISC/blob/master/JasonDecoder.php

它应该给你一些关于如何处理嵌套数组结构等的想法。

基本思路是你可以一次只添加一个标签,然后对那个标签进行解析。您可以在错误的位置对不同的模式类型执行尽可能多的错误检查,依此类推。它只是为一切提供了一个很好的结构。必要的是它使用preg_match_all和一串regx的方法,使用与方法相同的基本方法。主要区别在于它从数组构建完整的regx,然后使用数组键和命名捕获组(以及一些代码魔法),它允许您以更直观的方式引用它们。它还使用PREG_OFFSET_CAPTURE标志,我发现它比其他标志更快。

一个注意事项是标签的顺序非常重要,如果您先将T_UNKNOWN标记与所有标记匹配,那么它就不会转到它下面的标记。因此,他们应该更具体地匹配列表中的较高者。例如,你可以做这样的标签

    'T_IFNOT'           => '\*\|IFNOT:',

而不是我拥有的那个,但它可能必须在:

之前
    'T_TAG_START'       => '\*\|',

因为该标签会先与之匹配。

也不要忘记放next($lexer_stream);或者它将是一个无限循环。在嵌入式结构(如数组)中使用while和next来控制数组指针是必要的。

祝你好运,解析快乐!