查找字符串中不在 BBCodes 内的点
题
我有一个包含文章文本的字符串。其中散布着 BBCode(在方括号之间)。我需要能够抓住文章的第一个字符,即 200 个字符,而不会在 bbcode 中间将其切断。所以我需要一个可以安全地切断它的索引。这将为我提供文章摘要。
- 摘要必须至少为 200 个字符,但可以更长以从 bbcode 中“转义”。(这个长度值实际上是函数的参数)。
- 它不能在独立的 bbcode 中给我一个点(参见管道),如下所示:[列表|t]。
- 它不能给我一个开始和结束 bbcode 之间的点,如下所示:[url="http://www.google.com"]转到 Goo|gle[/url]。
- 在上面的示例中,它不能在开始或结束 bbcode 内或它们之间给我一个点。
它应该给我 200 之后的“安全”索引,并且不会切断任何 BBCode。
希望这是有道理的。我已经为此苦苦挣扎了一段时间。我的正则表达式技能只是中等。谢谢你的帮助!
解决方案
首先,我建议考虑如何处理完全包含在 BBcode 中的帖子,就像字体标签的情况一样。换句话说,对所述问题的解决方案很容易产生包含整篇文章的“摘要”。识别哪些标签仍然打开并附加必要的 BBcode 来关闭它们可能更有价值。当然,如果是链接,则需要额外的工作来确保不会破坏它。
其他提示
嗯,显而易见的 简单的 答案是呈现您的“摘要”,根本不带任何 bbcode 驱动的标记(下面的正则表达式取自 这里)
$summary = substr( preg_replace( '|[[\/\!]*?[^\[\]]*?]|si', '', $article ), 0, 200 );
但是,执行您明确描述的工作将需要的不仅仅是正则表达式。词法分析器/解析器可以解决这个问题,但这是一个中等复杂的主题。我看看能不能想出点什么。
编辑
这是词法分析器的一个漂亮的贫民窟版本,但对于这个例子来说它是有效的。这会将输入字符串转换为 bbcode 标记。
<?php
class SimpleBBCodeLexer
{
protected
$tokens = array()
, $patterns = array(
self::TOKEN_OPEN_TAG => "/\\[[a-z].*?\\]/"
, self::TOKEN_CLOSE_TAG => "/\\[\\/[a-z].*?\\]/"
);
const TOKEN_TEXT = 'TEXT';
const TOKEN_OPEN_TAG = 'OPEN_TAG';
const TOKEN_CLOSE_TAG = 'CLOSE_TAG';
public function __construct( $input )
{
for ( $i = 0, $l = strlen( $input ); $i < $l; $i++ )
{
$this->processChar( $input{$i} );
}
$this->processChar();
}
protected function processChar( $char=null )
{
static $tokenFragment = '';
$tokenFragment = $this->processTokenFragment( $tokenFragment );
if ( is_null( $char ) )
{
$this->addToken( $tokenFragment );
} else {
$tokenFragment .= $char;
}
}
protected function processTokenFragment( $tokenFragment )
{
foreach ( $this->patterns as $type => $pattern )
{
if ( preg_match( $pattern, $tokenFragment, $matches ) )
{
if ( $matches[0] != $tokenFragment )
{
$this->addToken( substr( $tokenFragment, 0, -( strlen( $matches[0] ) ) ) );
}
$this->addToken( $matches[0], $type );
return '';
}
}
return $tokenFragment;
}
protected function addToken( $token, $type=self::TOKEN_TEXT )
{
$this->tokens[] = array( $type => $token );
}
public function getTokens()
{
return $this->tokens;
}
}
$l = new SimpleBBCodeLexer( 'some [b]sample[/b] bbcode that [i] should [url="http://www.google.com"]support[/url] what [/i] you need.' );
echo '<pre>';
print_r( $l->getTokens() );
echo '</pre>';
下一步是创建一个解析器,循环遍历这些标记并在遇到每种类型时采取操作。或许我以后有时间再做吧...
这听起来不像是(仅)正则表达式的工作。“简单编程”逻辑是更好的选择:
- 抓取“[”以外的字符,增加一个计数器;
- 如果遇到开始标签,请继续前进,直到到达结束标签(不要增加计数器!);
- 当计数器达到 200 时,停止抓取文本。
这是一个开始。我目前无法访问 PHP,因此您可能需要进行一些调整才能使其运行。还有,这个 将不会 确保标签已关闭(即该字符串可以有 [url] 而没有 [/url])。另外,如果字符串无效(即并非所有方括号都匹配)它可能不会返回您想要的内容。
function getIndex($str, $minLen = 200)
{
//on short input, return the whole string
if(strlen($str) <= $minLen)
return strlen($str);
//get first minLen characters
$substr = substr($str, 0, $minLen);
//does it have a '[' that is not closed?
if(preg_match('/\[[^\]]*$/', $substr))
{
//find the next ']', if there is one
$pos = strpos($str, ']', $minLen);
//now, make the substr go all the way to that ']'
if($pos !== false)
$substr = substr($str, 0, $pos+1);
}
//now, it may be better to return $subStr, but you specifically
//asked for the index, which is the length of this substring.
return strlen($substr);
}
我编写了这个函数,它应该可以满足您的需求。它计算n个字符(标签中的字符除外),然后关闭需要关闭的标签。代码中包含示例使用。代码是用 python 编写的,但应该很容易移植到其他语言,例如 php。
def limit(input, length):
"""Splits a text after (length) characters, preserving bbcode"""
stack = []
counter = 0
output = ""
tag = ""
insideTag = 0 # 0 = Outside tag, 1 = Opening tag, 2 = Closing tag, 3 = Opening tag, parameters section
for i in input:
if counter >= length: # If we have reached the max length (add " and i == ' '") to not make it split in a word
break
elif i == '[': # If we have reached a tag
insideTag = 1
elif i == '/': # If we reach a slash...
if insideTag == 1: # And we are in an opening tag
insideTag = 2
elif i == '=': # If we have reached the parameters
if insideTag >= 1: # If we actually are in a tag
insideTag = 3
elif i == ']': # If we have reached the closing of a tag
if insideTag == 2: # If we are in a closing tag
stack.pop() # Pop the last tag, we closed it
elif insideTag >= 1:# If we are in a tag, parameters or not
stack.append(tag) # Add current tag to the tag-stack
if insideTag >= 0: # If are in some type of tag
insideTag = 0
tag = ""
elif insideTag == 0: # If we are not in a tag
counter += 1
elif insideTag <= 2: # If we are in a tag and not among the parameters
tag += i
output += i
while len(stack) > 0:
output += '[/'+stack.pop()+']' # Add the remaining tags
return output
cutText = limit('[font]This should be easy:[img]yippee.png[/img][i][u][url="http://www.stackoverflow.com"]Check out this site[/url][/u]Should be cut here somewhere [/i][/font]', 60)
print cutText