Search code examples
phpstringstring-operations

Find occurences of strings in string and replace - php


I'm currently working on my selfmade headline-system. Therefore I'm trying to use #_1# - #_4# for numbering in the text, which should be converted in headlines then. Therefore, you write the text of the headline between the number and the second #. For example:

#_1Testheadline#
#_2second headline under the first one#
#_2another headline under the first one#
#_1second headline (not under the first one)#

would become

1. Testheadline
1.1 second headline under the first one
1.2 another headline under the first one
2. second headline (not under the first one)

Hope you understand what I mean.

So the first example (so with #_1# in it) is written in a WYSIWYG-Editor, so this is normal html code, which is then stored in a variable in a database.

What I want now is when I get this variable from database, I want to replace all the pseudo-headlines by real headlines (to view), so the first example would become the second.

Therefore I need to find all occurences of #_1, replace with <h1>, go on until the # and replace it with </h1>. Same for the second, third and fourth "layer" of headings. If I'm able to do that, I still wouldn't have finished, because now, the headings need their numbers in front, therefore I could simply use 4 counters:

var firstlayercount = 0,
  secondlayercount = 0;
var thirdlayercount = 0,
  fourthlayercount = 0;

and increase and set them back to 0, depending on which headline was found, so actually this should be the easier part. But the harder question is, how to find the #_1 in the string, replace it and then go to the # and replace it, too? And do this several times?


Solution

  • Use preg_replace_callback function

    $str = '#_1Testheadline#
    #_2second headline under the first one
    #_2another headline under the first one
    #_1second headline (not under the first one)';
    
    // Current level
    $level = 0;
    echo preg_replace_callback('/#?\s*#_(\d)+/', 
      function($x) use (&$level) {
        // Close previous tag if it is
        $out = ($level ?  "</h{$level}>\n" : '');
        // Save current level and open tag  
        $level = $x[1];
        return $out .  "<h{$level}>"; 
      },
      // Don't forget to close the last tag
      $str) . "<h{$level}>";
    

    result

    <h1>Testheadline#</h1>
    <h2>second headline under the first one</h2>
    <h2>another headline under the first one</h2>
    <h1>second headline (not under the first one)<h1>  
    

    UPDATE 1

    You can use the same approach to build ordered list

    $level = 0;
    
    echo preg_replace_callback('/#?\s*#_(\d)+/', 
      function($x) use (&$level) { 
        if($x[1] > $level) $out = "\n<ol>\n";
        else if ($x[1] < $level) $out = "</li>\n</ol>\n";
        else $out = "</li>\n";
        $level = $x[1];
        return $out .  "<li>"; 
      },
      $str) . "</li>\n</ol>\n";
    

    result

    <ol>
       <li>Testheadline#
       <ol>
          <li>second headline under the first one</li>
          <li>another headline under the first one</li>
      </ol>
      <li>second headline (not under the first one)</li>
    </ol>
    

    UPDATE 2

    Stylise lists by css

    <STYLE>
    ol {
      list-style: none; 
      counter-reset: li; 
    }
    li:before {
       counter-increment: li; 
       content: counters(li,".") ". "; 
    }
    </STYLE>
    <ol>
       <li>Testheadline#
       <ol>
          <li>second headline under the first one</li>
          <li>another headline under the first one</li>
      </ol>
      <li>second headline (not under the first one)</li>
    </ol>


    UPDATE 3

    Making numbering by php

    $cnts = [ 0 ];
    echo preg_replace_callback('/#?\s*#_(\d)+/', 
      function($x) use (&$cnts) {
        $level = $x[1]-1;
        if($level < count($cnts)-1) $cnts = array_slice($cnts, 0, $level+1);
        if (! isset($cnts[$level])) $cnts[$level] = 1;
        else $cnts[$level]++;
        $temp = array_slice($cnts, 0, $level+1);
        return "\n" . implode('.', $temp) . ". "; 
      },
      $str) ;
    

    result

    1. Testheadline
    1.1. second headline under the first one
    1.2. another headline under the first one
    2. second headline (not under the first one)