From 4112d0e7a540e7c77c800791c9aeec8dfcb9c21d Mon Sep 17 00:00:00 2001 From: Sami Puro Date: Sat, 24 Oct 2026 17:11:03 +0300 Subject: [PATCH] Number the items of ordered lists --- src/Html2Text.php | 46 ++++++++++++++++++++++++++++++++++++++++++++++ test/ListTest.php | 24 +++++++++++++++++++++--- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/Html2Text.php b/src/Html2Text.php index 6e0f9e5..18ab23f 100644 --- a/src/Html2Text.php +++ b/src/Html2Text.php @@ -378,6 +378,7 @@ protected function converter(&$text) { $this->convertBlockquotes($text); $this->convertPre($text); + $this->convertOrderedLists($text); $text = preg_replace($this->search, $this->replace, $text); $text = preg_replace_callback($this->callbackSearch, array($this, 'pregCallback'), $text); $text = strip_tags($text); @@ -391,6 +392,9 @@ protected function converter(&$text) // This properly handles situation of "&quot;" in input string $text = str_replace('|+|amp|+|', '&', $text); + // Convert the "* |+|ol|+|N|+|" markers of ordered list items into "N. ", see convertOrderedLists() + $text = preg_replace('/\* \|\+\|ol\|\+\|(-?\d+)\|\+\|/', '\\1. ', $text); + // Normalise empty lines $text = preg_replace("/\n\s+\n/", "\n\n", $text); $text = preg_replace("/[\n]{3,}/", "\n\n", $text); @@ -501,6 +505,48 @@ protected function convertPre(&$text) } } + /** + * Helper function for numbering the items of ordered lists. + * + * Adds a "|+|ol|+|N|+|" marker after every LI tag of an OL list, which + * converter() turns into "N. " instead of the "* " bullet. Nested lists + * and the start attribute of OL are taken into account. + * + * @param string &$text HTML content + */ + protected function convertOrderedLists(&$text) + { + if (stripos($text, ']*>|]*>)/i', $text, -1, PREG_SPLIT_DELIM_CAPTURE); + // next item number of every open list, null for unordered lists + $lists = array(); + foreach ($parts as $i => $part) { + // even parts are the content between the tags + if ($i % 2 === 0) { + continue; + } + + if (preg_match('/^<(\/?)(ol|ul)\b/i', $part, $matches)) { + if ($matches[1] === '/') { + array_pop($lists); + } elseif (strtolower($matches[2]) === 'ol') { + $lists[] = preg_match('/\sstart\s*=\s*["\']?(-?\d+)/i', $part, $start) ? (int) $start[1] : 1; + } else { + $lists[] = null; + } + } elseif ($lists && end($lists) !== null) { + $level = count($lists) - 1; + $parts[$i] = $part . '|+|ol|+|' . $lists[$level] . '|+|'; + $lists[$level]++; + } + } + + $text = implode('', $parts); + } + /** * Helper function for BLOCKQUOTE body conversion. * diff --git a/test/ListTest.php b/test/ListTest.php index fb503d8..bd3b086 100644 --- a/test/ListTest.php +++ b/test/ListTest.php @@ -39,9 +39,9 @@ public function testOrderedList() EOT; $expected =<<<'EOT' - * Item 1 - * Item 2 - * Item 3 + 1. Item 1 + 2. Item 2 + 3. Item 3 EOT; @@ -49,4 +49,22 @@ public function testOrderedList() $html2text = new Html2Text($html); $this->assertEquals($expected, $html2text->getText()); } + + public function testOrderedListWithStart() + { + $html = '
  1. Item 5
  2. Item 6
'; + $expected = "\t5. Item 5\n\t6. Item 6"; + + $html2text = new Html2Text($html); + $this->assertEquals($expected, trim($html2text->getText(), "\n")); + } + + public function testNestedOrderedAndUnorderedLists() + { + $html = '
  1. One
    • Sub a
    • Sub b
  2. Two
    1. Sub 1
'; + $expected = "\t1. One\n\n\t* Sub a\n\t* Sub b\n\n\t2. Two\n\n\t1. Sub 1"; + + $html2text = new Html2Text($html); + $this->assertEquals($expected, trim($html2text->getText(), "\n")); + } }