', esc_attr($heading['anchor']) ); $replacement = preg_replace( '/^(]*>)/i', '$1' . $anchor_span, $heading['full_tag'] ); $content = $this->safeReplace($heading['full_tag'], $replacement, $content); } return $content; } public function injectToc(string $content, string $toc_html, string $position): string { return match($position) { 'before_first_heading' => $this->insertBeforeFirstHeading($content, $toc_html), 'after_first_heading' => $this->insertAfterFirstHeading($content, $toc_html), 'top' => $toc_html . $content, 'bottom' => $content . $toc_html, default => $this->insertBeforeFirstHeading($content, $toc_html), }; } private function insertBeforeFirstHeading(string $content, string $toc_html): string { if (preg_match('/]*>/i', $content, $match, PREG_OFFSET_CAPTURE)) { $position = $match[0][1]; return substr($content, 0, $position) . $toc_html . substr($content, $position); } return $toc_html . $content; } private function insertAfterFirstHeading(string $content, string $toc_html): string { if (preg_match('/<\/h[1-6]>/i', $content, $match, PREG_OFFSET_CAPTURE)) { $position = $match[0][1] + strlen($match[0][0]); return substr($content, 0, $position) . $toc_html . substr($content, $position); } return $toc_html . $content; } private function safeReplace(string $search, string $replace, string $subject): string { $pos = mb_strpos($subject, $search); if ($pos === false) { return $subject; } return mb_substr($subject, 0, $pos) . $replace . mb_substr($subject, $pos + mb_strlen($search)); } }