【问题标题】:Replace every character with an element用一个元素替换每个字符
【发布时间】:2013-04-30 10:09:25
【问题描述】:

这就是我所拥有的

$str = 'Just a <span class="green">little</span> -text åäö width 123#';

这就是我需要的

跨度和空格的结果,也可能是换行符。

$result = '<span></span><span></span><span></span><span></span> <span></span> <span class="green"><span></span><span></span><span></span><span></span><span></span><span></span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span>';

您可能想知道我可能需要这个做什么。我想构建一个用块表示字符的东西。看起来有点像 Windows XP 上的 Defrag。

问题

  • 将每个字符替换为&lt;span&gt;&lt;/span&gt;
  • 不要触摸字符串中已经存在的 HTML 跨度(可能很难?)。可以有多个 HTML 元素。
  • 请勿触摸空格和换行符。
  • Regexp 应该这样做吗?还是 Xpath?

到目前为止我做了什么?

我找到了关于正则表达式的文章,但没有替换每个字符(摘录空格和换行符)

$result = preg_replace("/???/", "<span></span>", $str);
print_r($result);

【问题讨论】:

  • try preg_replace("/([^:space:\n])/", "&lt;span&gt;&lt;/span&gt;", $str); [] 是一组字符,^ 不是,:space: 或 \s 是空格 \n 是换行符
  • “不要触摸字符串中已经存在的 HTML”部分是正则表达式解决方案导致问题的地方。您真的想使用 DOM 解析器,仅遍历文本节点并在这些节点上应用 /\S/ -> &lt;span&gt;&lt;/span&gt; 替换。 Here is a good overview of your DOM-parsing options
  • 只有一个 HTML span 还是还有更多?
  • 可能不止一个。我更新了我的问题信息。

标签: php regex string replace preg-replace


【解决方案1】:

您可以使用preg_replace_callback()

$str = 'Just a <span class="green">little</span> -text åäö width 123#';

function replacement($matches) {
            if (strlen($matches[0]) == 1) 
            {
                return "<span></span>";
            }
            else 
           {
               return $matches[0];
           }
}

$result = preg_replace_callback("~<span.*?<\s*/\s*span>|\S~", "replacement", $str);
print_r($result);

这只是根据匹配计算替换字符串。如果匹配的长度为 1(已找到非空白字符),则替换为“span”标签,否则已找到 span 标签,重新插入。

【讨论】:

  • @Waygood,不,因为\S是非空白字符,换行符属于空白字符,它们不匹配。
  • 还有什么东西也属于“空白字符”吗?如果它不只是一个空格和换行符,例如tab \t,那么结果会不会出错?
  • @Waygood 当然,制表符也是一个空格字符,因为它只打印空格。如果这是一个问题,则应使用否定字符类~&lt;span.*?&lt;\s*/\s*span&gt;|[^ \r\n]~。这将真正匹配不是空格或换行符的每个字符。
【解决方案2】:

不需要老套的正则表达式解决方案。带有状态机的简单 for 循环应该就可以了:

define('STATE_READING', 1);
define('STATE_TAG', 2);

$str = 'Just a <span class="green">little</span> -text åäö width 123#';
$result = '';

$state = STATE_READING;
for($i = 0, $len = strlen($str); $i < $len; $i++) {
    $chr = $str[$i];

    if($chr == '<') {
        $state = STATE_TAG;
        $result .= $chr;
    } else if($chr == '>') {
        $state = STATE_READING;
        $result .= $chr;
    } else if($state == STATE_TAG || strlen(trim($chr)) === 0) {
        $result .= $chr;
    } else {
        $result .= '<span></span>';
    }
}

如果我们正在读取标签或单个字符,则此循环仅用于跟踪。如果是标签(或空格),则追加实际字符,否则追加&lt;span&gt;&lt;/span&gt;

结果:

<span></span><span></span><span></span><span></span> <span></span> <span class="green"><span></span><span></span><span></span><span></span><span></span><span></span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span>

【讨论】:

  • 我更喜欢它而不是神奇的数字。 $state == STATE_TAG$state == 2$state == 'x' 更能显示意图。
【解决方案3】:

是否要求只使用一个正则表达式?

如果不是 - 你可以用一些独特的字符替换你需要安全的子字符串,用正则表达式执行替换,用子字符串代替那个独特的字符。

就像这样:

$str2 = str_replace('<span class="green">little</span>', '$', $str);
$str3 = preg_replace("/([^\s\n\$])/", "<span></span>", $str2);
$result = str_replace('$', '<span class="green">little</span>', $str3);

观看现场演示http://codepad.viper-7.com/7wu9fd

UPD:

也许它应该被视为提示。我的建议是存储需要存储的子字符串,替换您需要的所有内容,将存储的值放回字符串中。

$str = 'Just a <span class="green">little</span> -text åäö width 123#';

preg_match_all('/<[^>]+>/', $str, $matches);
$storage=array();
for($i=0, $n=count($matches[0]); $i<$n; $i++)
{
    $key=str_repeat('$', $i+1);
    $value=$matches[0][$i];
    $storage[$key]=$value;
    $str=str_replace($value, $key, $str);
}
$storage=array_reverse($storage);

$str = preg_replace("/([^\s\n\$])/", "<span></span>", $str);
foreach($storage as $k=>$v)
{
    $str=str_replace($k, $v, $str);
}
echo htmlspecialchars($str);

工作演示在那里http://codepad.viper-7.com/L4YZOz

【讨论】:

  • 有趣的解决方案。太糟糕了,这不是我的选择。 “小”可以是任何东西,也应该转换为跨度。
【解决方案4】:

虽然这可能使用正则表达式,但我会使用循环。下面的示例代码适用于单字节字符集,但可以针对多字节(例如 UTF-16)或可变字节(例如 UTF-8)字符集进行修改。

$input = 'Just a <span class="green">little</span> -text åäö width 123#';
$output = '';
$length = strlen($input);
$i = 0;
$matches = array(); // preg_match variable
// While for finer control
while($i < $length) {
    // Check for start of span tag, check for < character first for speed-up
    if($input[$i] == "<" && preg_match("#<span[^>]*>.*</span>#siU", substr($input, $i), $matches) == 1) {
        // Skip the span tag
        $i = $i + strlen($matches[0]);
        $output .= $matches[0];
    } else {
        $output .= "<span></span>";
        $i++;
    }
}

Working example

【讨论】:

  • 没有很好的测试代码,可能还有一些边界条件,但是思路应该很清楚了。
【解决方案5】:

这就是我使用preg_replace_callback() 得出的结论:

$str = 'Just a <span class="green">little</span>-text åäö width 123#<span>aaa</span> lol';

// This requires PHP 5.3+
$output = preg_replace_callback('#.*?(<span[^>]*>.*?</span>)|.*#is', function($m){
    if(!isset($m[1])){return preg_replace('/\S/', '<span></span>', $m[0]);}
    $array = explode($m[1], $m[0]);
    $array = preg_replace('/\S/', '<span></span>', $array);
    return(implode($m[1], $array));
}, $str);
echo($output);

输出:

<span></span><span></span><span></span><span></span> <span></span> <span class="green">little</span><span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span>aaa</span> <span></span><span></span><span></span>

【讨论】:

    【解决方案6】:

    有点小技巧,但试试这个:

    $str="Just a <span class=\"green\">little</span> -text åäö\n width 123#";
    
    // get all span tags
    if(preg_match_all("/(\<span.*\<\/span\>)/", $str, $matches))
    {
        // replace spans with #
        $str=preg_replace_all("/(\<span.*\<\/span\>)/", "#", $str);
    
        //print_r($matches);
    }
    // replace all non spaces, CR and #
    $str=preg_replace("/[^\s\n#]/", "<span></span>", $str);
    // replenish the matched spans
    while(list($key,$value)=each($matches[0]))
    {
        $str=preg_replace('/#/', $value, $str, 1);
    }
    

    【讨论】:

    • 如果$str 在两个跨度标签之间的某处包含#,这不会中断吗?
    • 是的,如果在 集合之外有 #,这就是为什么它是一个 hack
    【解决方案7】:

    这不是一个 hacky 正则表达式方法。这是一个可靠、简洁、单行一函数调用的解决方案,避免了对字符串中的每个字符重复一系列条件,保留标签,并处理多字节字符。

    alexn 的解决方案不保持åäö 的可见字符长度。他的解决方案将在屏幕上打印 6 个开始和结束跨度标签,而不仅仅是 3 个。这是因为没有使用 mb_ 函数。关于这个主题,请注意此页面上任何不使用 mb_ 前缀字符串函数的方法。

    我建议的解决方案将利用(*SKIP)(*FAIL) 技术来忽略/取消所有遇到的标签,然后只匹配字符串中的非空白字符。

    代码:(Demo)

    $str = 'Just a <span class="green">little</span> -text åäö width 123#';
    var_export(preg_replace('/<[^>]*>(*SKIP)(*FAIL)|\S/','<span></span>',$str));  // no "u" flag means åäö will be span x6
    echo "\n";
    var_export(preg_replace('/<[^>]*>(*SKIP)(*FAIL)|\S/u','<span></span>',$str)); // "u" flag means åäö will be span x3
    

    输出:(向右滚动查看 unicode 标志对模式的影响)

    '<span></span><span></span><span></span><span></span> <span></span> <span class="green"><span></span><span></span><span></span><span></span><span></span><span></span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span>'
    // notice the number of replacements for åäö ->-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------111111111111122222222222223333333333333444444444444455555555555556666666666666
    '<span></span><span></span><span></span><span></span> <span></span> <span class="green"><span></span><span></span><span></span><span></span><span></span><span></span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span> <span></span><span></span><span></span><span></span><span></span> <span></span><span></span><span></span><span></span>'
    // notice the number of replacements for åäö ->-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------111111111111122222222222223333333333333
    

    【讨论】:

    • @JensTörnell 在替换多字节字符时,您希望看到多少组 span 标签? åäö应该变成3套还是6套?在我看来,您只想要三个,因为六个没有额外的好处。
    猜你喜欢
    • 2021-06-07
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 2019-12-18
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    相关资源
    最近更新 更多