Hi :)

I'm haveing a little trouble working with a string which I need to insert a substring on certain positions.

Basically I'm trying to create a pagination for a wordpress site by inserting <!--nextpage--> on certain point in a content string. The content string is of various size from page to page and can contain HTML..so I need to insert the substring on a logical point without breaking the html.

My initial thought was to count the words and after x-number of words, search for the nearest closing html tag and insert the substring here.

So for example lets say that we insert the substring after every 700th word. Any help would be greatly appriciated :)

Dani AI

Generated

already had the right instincts (count words, find a nearby tag), but operating on the raw string leads to three predictable problems seen in this thread: 1) insertion next to an opening tag (because a simple strpos('>') can hit an opening tag), 2) repeated words on the next page (because the same text gets concatenated to both slices), and 3) the final tail being dropped (leftover chunk never pushed). A more robust, future-safe approach is to work with the HTML as a DOM, count words only in visible text nodes, and insert the comment node at a block-level element boundary — that avoids breaking markup, avoids duplication, and preserves the final fragment.

The practical recipe:

  • Load the post HTML into PHP's DOMDocument (wrap it in a temporary container so fragments are handled).
  • Use DOMXPath to iterate text nodes (exclude script/style/pre/code).
  • Keep a cumulative word count; when the threshold is reached, climb to the nearest block-level ancestor (p, div, blockquote, li, h1..h6, etc.) and insert a DOM comment node containing nextpage immediately after that element.
  • Serialize the wrapper's innerHTML back; the comments will appear as <!--nextpage--> and are safe for WordPress pagination.

Example (concise) implementation:

function insert_nextpage_comments($html, $wordsPerPage = 700) {
    $id = 'splitwrap_'.uniqid();
    $wrap = '<div id="'.$id.'">'.$html.'</div>';
    $doc = new DOMDocument();
    @$doc->loadHTML('<?xml encoding="utf-8"?>'.$wrap);
    $xpath = new DOMXPath($doc);
    $nodes = $xpath->query('//text()[normalize-space() and not(ancestor::script) and not(ancestor::style) and not(ancestor::pre) and not(ancestor::code)]');
    $count = 0;
    $blockTags = array('p','div','blockquote','ul','ol','li','table','tr','td','h1','h2','h3','h4','h5','h6','section','article','aside','figure','address','form');

    foreach ($nodes as $n) {
        $words = preg_split("/\s+/u", trim($n->nodeValue));
        $num = (empty($words[0])) ? 0 : count($words);
        $count += $num;
        if ($count >= $wordsPerPage) {
            $anc = $n->parentNode;
            while ($anc && !in_array(strtolower($anc->nodeName), $blockTags)) { $anc = $anc->parentNode; }
            if ($anc && $anc->parentNode) {
                $c = $doc->createComment('nextpage');
                $anc->parentNode->insertBefore($c, $anc->nextSibling);
                $count = 0;
            }
        }
    }

    $wrapper = $doc->getElementById($id);
    $out = '';
    if ($wrapper) {
        foreach ($wrapper->childNodes as $child) { $out .= $doc->saveHTML($child); }
    }
    return $out;
}

Notes and cautions: DOMDocument can reformat markup and add wrappers; test on a staging copy. Skip splitting inside pre/code to preserve code samples. For large sites, run this once when saving the post (save hook) rather than on every page render. If stricter splitting is desired, limit blockTags to only p and heading tags so pages break at paragraph/heading boundaries only.

Recommended Answers

All 4 Replies

Ok I think I might have a solution here. It works from a few tests that I have done, but forgive me but its late :)

Basically it is a function that takes a string, and the number of words you would like on each page. It splits the string by this amount. It then searches each page, finding the first close HTML tag. All text before this tag is added to the previous page, with the Newpage identifier added to it.

The function returns the pages as an array for easy sorting. You might want to do quite a bit of testing on this, and optimise it a little, as it might run a little slow. But once again it is late, and I hope this helps. :)

print_r (createPages($string,700));



function createPages($string, $numberOfWords ) 
{ 
    $words = preg_split('/\s/', $string); 
    $lines = array(); 
    $line = ''; 
    
    $countedWords = 0 ;
    
    foreach ($words as $k => $word) { 
        
    	$line .= $word . " ";
    	$countedWords++;
    	
    	if ( $countedWords == 700 )
    	{
    		$pages[] = $line;
    		$line = '';
    		$countedWords = 0;
    	}
    } 
    
    $pageCounter = 0;
    foreach ($pages as $page)
    {
    	if ($pageCounter)
    	{
    		$found = strpos($page, ">");
    		if ( $found ) 
    		{
    			$start = substr ( $page , 0 , $found ) ;
    			$pages [$pageCounter-1] .= " ". $start . "><!--nextpage-->";
    			$found = 0;
    			
    		}
    	}
    	
    	$pageCounter++;
    	
    }
    
    return $pages; 
}

Ok I think I might have a solution here. It works from a few tests that I have done, but forgive me but its late :)

Basically it is a function that takes a string, and the number of words you would like on each page. It splits the string by this amount. It then searches each page, finding the first close HTML tag. All text before this tag is added to the previous page, with the Newpage identifier added to it.

The function returns the pages as an array for easy sorting. You might want to do quite a bit of testing on this, and optimise it a little, as it might run a little slow. But once again it is late, and I hope this helps. :)

print_r (createPages($string,700));



function createPages($string, $numberOfWords ) 
{ 
    $words = preg_split('/\s/', $string); 
    $lines = array(); 
    $line = ''; 
    
    $countedWords = 0 ;
    
    foreach ($words as $k => $word) { 
        
    	$line .= $word . " ";
    	$countedWords++;
    	
    	if ( $countedWords == 700 )
    	{
    		$pages[] = $line;
    		$line = '';
    		$countedWords = 0;
    	}
    } 
    
    $pageCounter = 0;
    foreach ($pages as $page)
    {
    	if ($pageCounter)
    	{
    		$found = strpos($page, ">");
    		if ( $found ) 
    		{
    			$start = substr ( $page , 0 , $found ) ;
    			$pages [$pageCounter-1] .= " ". $start . "><!--nextpage-->";
    			$found = 0;
    			
    		}
    	}
    	
    	$pageCounter++;
    	
    }
    
    return $pages; 
}

Hi, thanks alot for your reply :)

It seems to be working aaaalmost perfectly :)

However..I'm encountering some smaller issues:

It sees to insert the substring after an opening html tag if there is no ending html tag nearby / if it encounters an opening html tag before a closing one after the position it searches for inside the main string.

For example in my case I noticed that it inserted the substring like this:

blabla main string her <blockquote><!--nextpage-->

Perhaps a way of checking if the first tag it encounters is a opening or closing html tag. If opening html tag, insert the substring BEFORE the tag, if closing tag, insert the substring AFTER the tag.

The second minor issues I encounter was that on, for example, page # 2, it echoed some words from the string which is before the nextpage substring.

Example:

$pages[0] = blabla SOME TEXT HERE <!--nextpage-->
$pages[1] = SOME TEXT HERE blabla

So as you see, "SOME TEXT HERE" gets repeated on the next page :)

Thanks alot in advance for helping me out here :)

Hi, thanks alot for your reply :)

It seems to be working aaaalmost perfectly :)

However..I'm encountering some smaller issues:

It sees to insert the substring after an opening html tag if there is no ending html tag nearby / if it encounters an opening html tag before a closing one after the position it searches for inside the main string.

For example in my case I noticed that it inserted the substring like this:

blabla main string her <blockquote><!--nextpage-->

Perhaps a way of checking if the first tag it encounters is a opening or closing html tag. If opening html tag, insert the substring BEFORE the tag, if closing tag, insert the substring AFTER the tag.

The second minor issues I encounter was that on, for example, page # 2, it echoed some words from the string which is before the nextpage substring.

Example:

$pages[0] = blabla SOME TEXT HERE <!--nextpage-->
$pages[1] = SOME TEXT HERE blabla

So as you see, "SOME TEXT HERE" gets repeated on the next page :)

Thanks alot in advance for helping me out here :)

Oh yeah..it also seems to cut off any remaining part of the original string which might be left AFTER the last occurance of the

<!--nextpage-->

.

Guessing this has something to do that it increases the counter only when the counter reaches 700 words..? So if the last remaining paft has less than 700 words it doesn't save this part of the main string into the array :/

Well what you could do, where I am searching for '>' you could turn it to search for '</'. Then use this number for the offset then search again for '>'. Then once you have that then concatenate <!--nextpage--> to the end of that value?

As for loosing the final part of the text, sorry I didn't test for that but it was late lol. What you could do, you could do a stringLenght check on each section of the array as it loops round. Then add this together, to get a total length. Once you have this, then create a new string based on original string, then use the number you have as the start, till the end of the original string. This will give you the remaining part of the text. Then push this into a new element of the array.

Well I hope that helps :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.