テキストの目次を生成する

良い一日! この出版物では、PHPでテキストの目次を生成する方法について説明し、話します。 Laravel Hubはなぜですか? このソリューションにより、Composerを介して簡単に接続できるパッケージが作成されました。







私はコードリーダーです! コードが必要な場合は、 こちらにあります 。 最終的なソリューションにより、Laravelのパッケージが作成されました。



座っているということは、私たちが男の子と一緒にコーディングするということです...興味深いプロジェクトに取り組んでいるタスクは、お客様からのものです。 このタスクは昨年私に飛びつきました。 どのように、どのように実装するかは理解できず、不明でした。 私に合った結果の有名な検索エンジンは、このトピックでは生成しませんでした。 自分で決断を下す必要がありました。 また、既成のソリューションを備えた有名なポータルの1つも役に立ちませんでした。



私は座って、数リットルの暗闇を叩きました...私はそうする方法を考えて自分自身を集め、コーディングを始めました。 最初は、すべてがシンプルに見えました。テキストにはさまざまなレベルの見出し(h1〜h6タグ)があり、目次を作成する必要があります。 さて、テンプレートとこの全体に基づいてテキストを配列に解析すると思います。 仕事中、ミューズが私を訪ね、対話が始まりました。

-エンコーダー、ヘッダーの順序が厳密でない場合はどうしますか?

-何? どう?

-h1が移動し、テキストh2、h3、再びh2、h1、h2、h3、h4に移動すると想像してください。 これは厳密な順序です。なぜなら、 下位レベルの見出しは、厳密に親に従います。 ここで、h2の後、テキストはh3ではなくh4またはh6に続くと想像してください! そして、h6、h4などの後、イコライザーのように。

ここで私は冷たい汗で覆われました。 「まあ」、「これはありえない」と思う。 私は顧客に確認することにしました! これは可能です!..



私は悪い考えを持っていました...お茶を一杯飲んで、脳を失ったので 、私はアレイで降りないことに決めました、なぜなら 親がいつ開閉するかを知る必要があります。 配列はそこにあるかどうかにかかわらず、次のヘッダーを保存する必要がある場所を正確に判断することはできません。 なぜなら これらはすべて、API、SPAで発生します。ヘッダーの各レベルは、左端から独自の距離にある必要があります。 さらに、タイトル自体とそのリンクも保存する必要があります。 リンクはタイトル自体から形成され、クリックしてテキストの目的の部分に収まるようにアンカーにする必要があります。



難しくはありませんが、おもしろいです...それが私自身の考えであり、JSONでデータを収集することで親要素と子要素を開閉できると考えました。



コードの最初の行は次のとおりです。



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





彼女はすぐにはリーダーになりませんでしたが、wysiwygエディターにはないh7-h10ヘッダーを挿入できるという要件が追加された後に登場しました。 このために、独自の魔法が作成されました。その結果、タイトルタグを段落で囲むことができる状況になりました。 ここに指定された行があり、このまさに段落(pタグ)を削除します。



次に、$ items配列内のテキストにあるすべてのヘッダーを配列に収集します



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







そして、カルーセルが始まります。 まず、目次全体を開く必要があります。



 $menu = "{";
      
      





次に、見出しの下で大きなサイクルが開始され、そこに飛び込みます:



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





まず、ヘッダーのテキストを取得して、タグやその他の殻を削除する必要があります。 replaceH1Symbols関数は、一部のhtmlエンティティを特殊文字に置き換えます (たとえば、<turns into ")。 stripTagsプロパティには、このような通常の



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





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





名前を受け取ったら、そのリンクを作成します。



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





symbolsプロパティはすべての破壊文字(引用符、角かっこ、アポストロフィ、句読点など)を格納し、 スペースではすべてがスペースに見えます。リンク内にあるべきではないためです。



そのため、リンクがあります。 次に、テキスト内にそのようなリンクが既にあるかどうかを確認する必要があります。 実際、テキストには同じ見出しが含まれている場合があります。 そのようなリンクがあり、おそらくそれだけではない場合は、新しいリンクにシリアル番号を割り当てる必要があります



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





殺して! 目次を作成し始めます。 最初に、サイクルの開始があるかどうかを確認します



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





始まりの場合、すべてがうまくいきますが、そうでない場合は? 現在のヘッダーのレベルが前のヘッダー(たとえば、前のh2、現在のh4)よりも大きいかどうかを確認します。 おそらくこの言い回しは完全に正しいわけではありませんが、hに近い数字から始めて、多かれ少なかれ書いています。



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





その場合、これらのヘッダー間の差を計算する必要があります。



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





子サブメニューを開きます



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





次に、前の見出しがあるレベルを記録します。 順序が小さい場合(2 <4)、現在のヘッダーの親になります。



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





そして、内部要素の総数を書きます:



 $subItemsCount += $quantity;
      
      





次に、存在しないこれらの非常に内部的な要素のネストサイクルを開始します。



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





そして、最後にヘッダーを挿入します



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





これはどのように見えますか? 次に、現在のタイトルが前のタイトルよりも小さい場合の逆の状況を考えます。 たとえば、h2はh4の後にあります。



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





次に、ヘッダー間の差を計算し、 subItemsCountがある場合は、以前に開いたすべての内部要素を閉じる必要があります。 なぜ2倍するのかと尋ねますか? 信じて、ただ信じて。 これは魔法であり、かつては説明がありましたが、今では開き/閉じ中括弧のペアに関する神話で覆われています。



 $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); } } }
      
      





そして現在のタイトルを挿入します



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





ねえ、あなたは麻薬中毒者ですか? 現在の見出しのレベルが前のものと等しいかどうかを確認する最後のチェック。 たとえば、h2があり、再びh2がありました。 ここではすべてが簡単です。前の要素を閉じて、現在の要素を挿入します。



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





約束することは結婚することを意味するものではありません...欺かれ、最後ではありません。 現在のヘッダーが最後かどうかを確認する最後のチェック。 結局のところ、もしそうなら、彼に開いているすべてのレベルを閉じる必要があります。



 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 .= "}"; } }
      
      





そして、使用する名前の配列に名前を入れることを忘れないでください:



 $usedItem[] = $name;
      
      





これで話は終わりです。目次を閉じるのはサイクルの外側に限られます



 $menu .= "}";
      
      





それだけです 目次はすぐに使用できます。 ヘッダー自体にアンカーを配置するのはテキストのみです。



なに? APIが終了し、SPAがオブジェクトを受信すると、このJSONがデコードされます。



読みました、よくできましたか? ご清聴ありがとうございました。この出版物が役立つことを願っています。 アドバイス、批判は24 \ 7を受け入れます。 コーディングだけでなくみんなに幸運を!



All Articles