需要一个正则表达式将css类添加到第一个和最后一个列表项

时间:2008-11-30 08:11:55

标签: php html css regex

更新: 谢谢大家的意见。一些额外的信息。

这只是我正在使用的一小部分标记(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); 

非常感谢任何帮助。

6 个答案:

答案 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);
  }
}