2
\$\begingroup\$

Let's keep this simple.

Input:

- abc
- def
- ghi

Desired output (whitespace optional):

<ul>
    <li>abc</li>
    <li>def</li>
    <li>ghi</li>
</ul>

Current code:

$output = preg_replace_callback(
    "((^- (.*)$\r*\n*)+)m",
    function($m) {
        return "<ul><li>".
            implode(
                "</li><li>",
                preg_split(
                    "(^- )m",
                    substr($m[0],2)
                )
            ).
            "</li></ul>";
    },
    $input
);

It works, the output is exactly what I want, but it seems kinda... hackish. Having a preg_split inside a preg_replace_callback seems a bit redundant, but I can't seem to find a way of capturing subpatterns that are inside a quantifier. If I could do that then I would implode the subpatterns rather than having to split the matched part again.

Any suggestions?

\$\endgroup\$
2
  • \$\begingroup\$ Do you need to handle nested lists? Because that won't work with a regex. \$\endgroup\$ Feb 22, 2012 at 10:11
  • \$\begingroup\$ Nested lists would be nice, but not required. That said, if you know a good piece of code for nested lists, please do share. \$\endgroup\$ Feb 22, 2012 at 10:31

2 Answers 2

1
\$\begingroup\$

Wouldn't this give you the desired output:

$result = '<ul>' . preg_replace('/^- (.*)$/m', '<li>$1</li>', $subject) . '</ul>';

Update:

$output = preg_replace_callback(
    "((^- (.*)$\r*\n*)+)m",
    function($m) {
        return '<ul>' . preg_replace('/^- (.*)$/m', '<li>$1</li>', $m) . '</ul>';
    },
    $input
);
\$\endgroup\$
2
  • \$\begingroup\$ Not really, because the list may be inside a bigger string - I guess I should've mentioned that, but the string might have text before and after the list. \$\endgroup\$ Feb 22, 2012 at 10:09
  • \$\begingroup\$ Oh, I see. My option will place the ul before all text, instead of before the list items. You could however use the replace in your callback. \$\endgroup\$
    – pritaeas
    Feb 22, 2012 at 10:31
1
\$\begingroup\$

Another option is to do this in two passes:

  1. replace - foo by <li>foo</li> in every line
  2. detect blocks of list items and add <ul></ul> around them

The code looks pretty straigtforward to me:

$output_li = preg_replace("/^- (.*)$/m", "<li>$1</li>", $input);
$output = preg_replace("/((<li>.*<\/li>\n)+)/m", "<ul>\n$1</ul>\n", $output_li);

If you want to allow nested lists, you might also want to allow other constructions. Why not use a php port of markdown then? If it's only about nested lists, a way to do that would be to use a recursive descent parser which would call itself whenever there is a new indentation level.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ What if there's already a list? You would end up wrapping an extra <ul>..</ul> around it... \$\endgroup\$ Feb 22, 2012 at 10:52
  • \$\begingroup\$ You could add a third pass to remove them, but it would then be more hackish than your original solution. :) \$\endgroup\$ Feb 22, 2012 at 11:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.