更新: 谢谢大家的意见。一些额外的信息。
这只是我正在使用的一小部分标记(20行),并且旨在利用正则表达式来完成工作。
我也有能力破解脚本(电子商务)以在构建导航时插入类。当我要更新到最新版本的软件时,我想限制我所拥有的黑客数量,以便让自己更容易。
话虽如此,我很清楚我的情况以及我可以使用的各种选择。我的正则表达式的第一部分按预期工作。我真的或多或少地贴出来看看有人会说,“嘿假,这很容易改变这个.....”
在接近我的一些努力之后,现在更多的是原则。要知道(并学习)这个问题的解决方案。我也讨厌被一段代码殴打。
ORIGINAL:
我正在尝试利用正则表达式将CSS类添加到有序列表中的第一个和最后一个列表项。我尝试了很多不同的方法,但无法产生我正在寻找的结果。
我有第一个列表项的正则表达式,但似乎无法找到最后一个正确的表达式。以下是我正在使用的内容:
$patterns = array('/<ul+([^<]*)<li/m', '/<([^<]*)(?<=<li)(.*)<\/ul>/s');
$replace = array('<ul$1<li class="first"','<li class="last"$2$3</ul>');
$navigation = preg_replace($patterns, $replace, $navigation);
非常感谢任何帮助。
答案 0 :(得分:3)
Jamie Zawinski会有something to say about this ...
你有一个合适的HTML解析器吗?我不知道是否有类似hpricot的可用于PHP的东西,但这是处理它的正确方法。您至少可以使用hpricot为您进行第一次清理。
如果您实际上正在生成HTML - 请在那里进行。看起来您想要生成一些导航,并在其上添加.first
和.last
类型的东西。退一步试试。
答案 1 :(得分:2)
+1以生成正确的html作为最佳选择。
但是一种完全不同的方法,可能会或可能不会被您接受:您可以使用javascript。
这使用jquery让它变得简单......
$(document).ready(
function() {
$('#id-of-ul:firstChild').addClass('first');
$('#id-of-ul:lastChild').addClass('last');
}
);
正如我所说,在这种情况下可能有也可能没用,但我认为在某些情况下它是解决问题的有效方法。
PS:你说有序列表,然后在你的例子中给出ul。 ol =有序列表,ul =无序列表
答案 2 :(得分:1)
您写道:
$patterns = array('/<ul+([^<]*)<li/m','/<([^<]*)(?<=<li)(.*)<\/ul>/s');
第一种模式:
ul+
=&gt;你搜索类似ullll的东西......
m修饰符在这里没用,因为你不使用^或$。
第二种模式:
使用。*和s是“危险的”,因为你可以选择整个文档直到页面的最后一个/ ul ...
好吧,我只需删除修饰符并使用(<li\s)(.*?</li>\s*</ul>)
替换:'$1class="last" $2'
鉴于上述评论,我会写第一个表达式:<ul.*?>\s*<li
虽然每次有正则表达式问题时我都厌倦了看到Jamie Zawinski的引用,但Dustin指出你是一个HTML解析器(或者只是从一开始就生成正确的HTML!):正则表达式和HTML没有混合得很好,因为HTML语法很复杂,除非你对一个众所周知的机器生成的输出采取非常可预测的结果,否则在某些情况下你很容易出现问题。
答案 3 :(得分:1)
我不知道是否有人关心,但我有一个解决方案适用于我的简单测试用例(我相信它应该适用于一般情况)。
首先,让我指出两件事:虽然PhiLho是正确的,因为 s 是“危险的”,因为点可能匹配到文档最后的所有内容,这很可能是你想要什么。只有形状不好的页面才会成为问题。在手动编写的大页面上要小心任何这样的正则表达式。
其次,php具有反斜杠的特殊含义,即使在单引号中也是如此。大多数regexen都会表现良好,但为了以防万一,你应该总是双重逃避它们。
现在,这是我的代码:
<?php
$navigation='<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
<li>Beer</li>
<li>Water</li>
</ul>';
$patterns = array('/<ul.*?>\\s*<li/',
'/<li((.(?<!<li))*?<\\/ul>)/s');
$replace = array('$0 class="first"',
'<li class="last"$1');
$navigation = preg_replace($patterns, $replace, $navigation);
echo $navigation;
?>
这将输出
<ul>
<li class="first">Coffee</li>
<li>Tea</li>
<li>Milk</li>
<li>Beer</li>
<li class="last">Water</li>
</ul>
假设开口内没有换行&lt; ul ...&gt;标签。如果有,请在第一个表达式上使用 s 修饰符。
神奇发生在(.(?<!<li))*?
。这将匹配任何不是字符串&lt; li 开头的字符(点),以非贪婪的方式(?)重复任何次数(*)。
当然,如果列表项有可能已经设置了 class 属性,则必须扩展整个内容。此外,如果只有一个列表项,它将匹配两次,给它两个这样的属性。至少对于xhtml,这会破坏验证。
答案 4 :(得分:0)
您可以在SimpleXML对象中加载导航并使用它。这可以防止你用一些疯狂的正则表达式打破你的标记:)
答案 5 :(得分:0)
作为序言..在大多数用例中,这是过于复杂的事情。请参阅其他答案以获得更多理智:)
这是我为解决类似问题而编写的一个小类。它添加了“第一个”,“最后一个”以及您想要的任何其他类。它将处理没有“class”属性的li,以及那些已经有一些类的属性。
<?php
/**
* Modify list items in pre-rendered html.
*
* Usage Example:
* $replaced_text = ListAlter::addClasses($original_html, array('cool', 'awsome'));
*/
class ListAlter {
private $classes = array();
private $classes_found = FALSE;
private $count = 0;
private $total = 0;
// No public instances.
private function __construct() {}
/**
* Adds 'first', 'last', and any extra classes you want.
*/
static function addClasses($html, $extra_classes = array()) {
$instance = new self();
$instance->classes = $extra_classes;
$total = preg_match_all('~<li([^>]*?)>~', $html, $matches);
$instance->total = $total ? $total : 0;
return preg_replace_callback('~<li([^>]*?)>~', array($instance, 'processListItem'), $html);
}
private function processListItem($matches) {
$this->count++;
$this->classes_found = FALSE;
$processed = preg_replace_callback('~(\w+)="(.*?)"~', array($this, 'appendClasses'), $matches[0]);
if (!$this->classes_found) {
$classes = $this->classes;
if ($this->count == 1) {
$classes[] = 'first';
}
if ($this->count == $this->total) {
$classes[] = 'last';
}
if (!empty($classes)) {
$processed = rtrim($matches[0], '>') . ' class="' . implode(' ', $classes) . '">';
}
}
return $processed;
}
private function appendClasses($matches) {
array_shift($matches);
list($name, $value) = $matches;
if ($name == 'class') {
$value = array_filter(explode(' ', $value));
$value = array_merge($value, $this->classes);
if ($this->count == 1) {
$value[] = 'first';
}
if ($this->count == $this->total) {
$value[] = 'last';
}
$value = implode(' ', $value);
$this->classes_found = TRUE;
}
return sprintf('%s="%s"', $name, $value);
}
}