📜 ⬆️ ⬇️

We generate a table of contents for the text

Good day! In this publication I want to tell and tell you how to generate a table of contents for PHP. Why hub "Laravel"? This solution resulted in a package that can be simply connected via composer.



I am a code reader! If you need a code, it's here . The final decision resulted in a package for Laravel.

We are sitting with the boys, the code ... While working on an interesting project, the customer receives the task “Guys, it’s necessary for a certain type of articles to generate a table of contents on the fly”. This task came to me last year. How, how to implement it was incomprehensible and unknown. One famous search engine didn’t give out any results that would suit me. It was necessary to come up with the decision himself. And one famous portal with ready-made solutions did not help me either.
')
I sat down, banged a couple of liters of dark ... I got together with the idea that yes how to do and began to code. At first everything seemed simple: there are headings of different levels in the text (h1-h6 tags), and we need to build a table of contents on them. Well, I think, now I will parse the text on the template and the whole thing into an array. While working, the muse visited me and the dialogue began:
- coder, and what will you do if the headers are not in a strict order?
- what? like this?
- Imagine, going h1, then in the text h2, h3, again h2, h1, h2, h3, h4. This is a strict order, because lower-level headings follow the parent strictly. And now imagine that after h2, it is not h3 that goes in the text, but h4 or h6! And after h6 h4 and so on, like an equalizer.
Here I was covered with cold sweat. “Well,” I think, “this cannot be.” I decided to check with the customer and yes! Is it possible!..

I gave a shit to gov ... Having drunk a cup of tea, having made up my mind, I decided that I could not get rid of the array, because I need to know when the parent to open and close. But the array is either there, or it is not there, and it’s impossible to determine exactly where I need to save the next title. Since All these things happen on the API, on the SPA, respectively, each header level should be at a distance from the left edge. In addition, it is necessary to keep the title itself and a link to it. The link is formed from the title itself and must be an anchor in order to click and get into the necessary part of the text.

Not hard, but interesting ... That's exactly what I thought to myself, and I also thought that I can open and close the parent and child elements myself, collecting data in JSON.

The first line of code is:

$description = preg_replace("/<(p|[hH](10|[1-9]))>(<[hH](10|[1-9]).*?>(.*?)<\/[hH](10|[1-9])>)<\/(p|[hH](10|[1-9]))>/", "$3", $description); 

She did not immediately take the lead, but appeared after the requirement was added that it should be possible to insert the headers h7-h10, which were not in the wysiwyg editor. For this, we created our own magic, as a result of which we received a situation when the title tag could be framed with a paragraph. This is the line that removes this very paragraph (p tag).

Next we collect into the array all the headers that are in the text in the array $ items

 preg_match_all("/<[hH](10|[1-9]).*?>(.*?)<\/[hH](10|[1-9])>/", $description, $items); 


And then the carousel begins. First I need to open all my table of contents:

 $menu = "{"; 

Then, a large cycle is launched on the headings, into which we will dive:

 for ($i = 0; $i < count($items[0]); $i++) {...} 

First of all, you need to get the title text, clearing it from tags and other peels. The replaceH1Symbols function replaces some html entities with special characters (for example, <turns into “). The stripTags property stores such a regular

 /<\/?[^>]+>|\&[az]+;|\'|"/ 

 $name = preg_replace($this->stripTags, "", trim(html_entity_decode($this->replaceH1Symbols($items[2][$i]), ENT_QUOTES))); 

After the name is received, we will create a link for it:

 $link = preg_replace($this->symbols, "", strtolower($name)); $link = preg_replace($this->spaces, "-", $link); 

where the symbols property contains all the vzabinka symbols (quotes, brackets, apostrophes, punctuation marks, etc.), and in spaces everything looks like a space, because it should not be in the link.

So, there is a link. Next you need to check whether there is such a link already in the text. After all, the text may contain the same headings. If there is such a link and perhaps it is not one, then our new link should be assigned its serial number

 $repeatCount = count(array_keys($usedItem, $name)); if ($repeatCount > 0) { $link .= "-" . ($repeatCount + 1); } 

Kill me! We begin to form our table of contents. First, check whether we have a cycle or not.

 if ($i == 0) { $menu .= '"' . $i . '": {'; $menu .= '"title": "' . $name . '",'; $menu .= '"link": "' . $link . '"'; } 

If the beginning, then everything is fine, and if not? We check for whether the level of the current header is greater than the previous one (for example, the previous h2, and the current h4). Perhaps this wording is not entirely correct, but I write more \ less, starting from the number near h.

 elseif ($i != 0 && $items[1][$i] > $items[1][$i - 1]) { 

If so, we need to calculate the difference between these headers.

 $quantity = $items[1][$i] - $items[1][$i - 1]; 

and open a child submenu

 $menu .= ', "subItems": {'; 

Then we write down what level our previous heading is, because if its order is less (2 <4), then it is the parent of our current title.

 array_push($parentItem, (int)$items[1][$i - 1]); 

And write the total number of internal elements:

 $subItemsCount += $quantity; 

Then we start the nesting cycle of these very internal elements, which are not.

 for ($j = 1; $j <= $quantity - 1; $j++) { $menu .= "\"" . $j . "\":{"; $menu .= '"subItems": {'; array_push($parentItem, $items[1][$i - 1] + $j); } 

And after that, finally insert our title

  $menu .= '"' . $i . '": {'; $menu .= '"title": "' . $name . '",'; $menu .= '"link": "' . $link . '"'; } 

How would I get it? Then we consider the situation on the contrary, when the current title is less than the previous one. For example, h2 comes after h4.

 elseif ($i != 0 && $items[1][$i] < $items[1][$i - 1]) { 

Then we calculate the difference between the headers and, if there is a subItemsCount, we need to close all the internal elements that were opened earlier. You ask why I multiply by 2? Believe, just believe. This is a magic that previously had an explanation, but now it is covered with myths about the pairing of opening / closing curly braces.

 $quantity = $items[1][$i - 1] - $items[1][$i]; $menu .= "}"; if ($subItemsCount) { for ($j = 1; $j <= $quantity * 2; $j++) { $menu .= "}"; if ($j % 2 == 0) { $subItemsCount--; array_pop($parentItem); } } } 

And paste our current header

  $menu .= ', "' . $i . '": {'; $menu .= '"title": "' . $name . '",'; $menu .= '"link": "' . $link . '"'; } 

Hey man, are you a drug addict? The last check is whether the level of the current header is equal to the previous one. For example, there was h2 and again h2. It's simple: close the previous item and insert the current one.

 else { $menu .= '}, "' . $i . '": {'; $menu .= '"title": "' . $name . '",'; $menu .= '"link": "' . $link . '"'; } 

To promise is not to marry ... He deceived, not the last. Last check on whether the current header is the last or not. After all, if so, we need to close all levels open to it.

 if (!array_key_exists($i + 1, $items[1])) { $a = $items[1][$i]; $lastParent = array_shift($parentItem); if ($lastParent && $lastParent < $a) { for ($q = 0; $q <= ($a - $lastParent) * 2; $q++) { $menu .= "}"; } } else { $menu .= "}"; } } 

And do not forget to put the name in the array of used names:

 $usedItem[] = $name; 

That's the end of the fairy tale. It remains only to close our table of contents outside the cycle.

 $menu .= "}"; 

Everything. Table of contents is ready to use. It remains only to put anchors in the text on the headers themselves.

Shta This JSON at the exit of the API is decoded and the SPA receives the object.

I read, I do well? Thank you for your attention and I hope that the publication will be useful. Any advice, criticism accept 24 \ 7. Good luck to all coding and not only!

Source: https://habr.com/ru/post/325224/


All Articles