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\$ – Quentin Pradet Feb 22 '12 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\$ – Niet the Dark Absol Feb 22 '12 at 10:31
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\$ – Niet the Dark Absol Feb 22 '12 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 '12 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\$ – Niet the Dark Absol Feb 22 '12 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\$ – Quentin Pradet Feb 22 '12 at 11:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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