'', 'attrs' => [], 'inlineAttrs' => [] ]; foreach ($content['ops'] as $op) { $text = ''; $attrs = isset($op['attributes']) ? $op['attributes'] : []; // Gérer l'insertion de texte if (is_string($op['insert'])) { $text = $op['insert']; // Si c'est un saut de ligne, on finalise le bloc if ($text === "\n") { // Vérifier les attributs de bloc pour ce saut de ligne foreach ($attrs as $key => $value) { if (in_array($key, self::$blockAttrs)) { $currentBlock['attrs'][$key] = $value; } } // Finaliser le bloc courant if (!empty($currentBlock['content']) || !empty($currentBlock['attrs'])) { $html .= self::renderBlock($currentBlock); } // Réinitialiser pour le prochain bloc $currentBlock = [ 'content' => '', 'attrs' => [], 'inlineAttrs' => [] ]; } else { // Ajouter le texte avec ses attributs inline $currentBlock['content'] .= $text; if (!empty($attrs)) { $currentBlock['inlineAttrs'][] = [ 'start' => mb_strlen($currentBlock['content']) - mb_strlen($text), 'length' => mb_strlen($text), 'attrs' => $attrs ]; } } } // Gérer les images elseif (is_array($op['insert']) && isset($op['insert']['image'])) { if (!empty($currentBlock['content'])) { $html .= self::renderBlock($currentBlock); $currentBlock = [ 'content' => '', 'attrs' => [], 'inlineAttrs' => [] ]; } $imageUrl = self::cleanImageUrl($op['insert']['image']); $imgAttributes = ['class' => 'chapter-image']; if (isset($attrs['alt'])) { $imgAttributes['alt'] = $attrs['alt']; } else { $imgAttributes['alt'] = "Image"; } $html .= self::createImageTag($imageUrl, $imgAttributes); } } // Finaliser le dernier bloc si nécessaire if (!empty($currentBlock['content'])) { $html .= self::renderBlock($currentBlock); } return $html; } private static function renderBlock($block) { if (empty($block['content'])) return ''; $tag = 'p'; $styles = []; $classes = []; // Déterminer le tag et attributs de base if (!empty($block['attrs']['header'])) { $tag = 'h' . $block['attrs']['header']; } elseif (!empty($block['attrs']['blockquote'])) { $tag = 'blockquote'; } elseif (!empty($block['attrs']['code-block'])) { $tag = 'pre'; } // Appliquer l'alignement au niveau du bloc if (!empty($block['attrs']['align'])) { $styles[] = "text-align: " . $block['attrs']['align'] . " !important"; } // Appliquer la police au niveau du bloc si présente if (!empty($block['attrs']['font'])) { $classes[] = 'font-' . strtolower($block['attrs']['font']); } // Construire le contenu avec les attributs inline $content = $block['content']; if (!empty($block['inlineAttrs'])) { // Trier les attributs par position de fin pour les appliquer de la fin vers le début usort($block['inlineAttrs'], function($a, $b) { return ($a['start'] + $a['length']) - ($b['start'] + $b['length']); }); // Appliquer les attributs inline foreach (array_reverse($block['inlineAttrs']) as $attr) { $start = $attr['start']; $length = $attr['length']; $segment = mb_substr($content, $start, $length); if (!empty($attr['attrs'])) { $segment = self::applyInlineAttributes($segment, $attr['attrs']); } $content = mb_substr($content, 0, $start) . $segment . mb_substr($content, $start + $length); } } // Si c'est un bloc de code, wrapper dans code if ($tag === 'pre') { $content = "$content"; } // Construire les attributs HTML finaux $attributes = ''; if (!empty($styles)) { $attributes .= ' style="' . implode('; ', $styles) . '"'; } if (!empty($classes)) { $attributes .= ' class="' . implode(' ', $classes) . '"'; } return "<$tag$attributes>$content"; } private static function applyInlineAttributes($text, $attrs) { $text = htmlspecialchars($text); if (!empty($attrs['bold'])) { $text = "$text"; } if (!empty($attrs['italic'])) { $text = "$text"; } if (!empty($attrs['underline'])) { $text = "$text"; } if (!empty($attrs['color'])) { $text = "$text"; } if (!empty($attrs['background'])) { $text = "$text"; } if (!empty($attrs['link'])) { $text = "$text"; } if (!empty($attrs['font'])) { $text = "$text"; } if (!empty($attrs['script'])) { if ($attrs['script'] === 'super') $text = "$text"; if ($attrs['script'] === 'sub') $text = "$text"; } return $text; } private static function isJson($string) { json_decode($string); return json_last_error() === JSON_ERROR_NONE; } private static function cleanImageUrl($url) { return preg_replace('/^(?:\.\.\/)+/', '', $url); } private static function cleanImageUrls($html) { return preg_replace_callback( '/]+src=([\'"])((?:\.\.\/)*(?:assets\/[^"\']+))\1[^>]*>/', function($matches) { $cleanUrl = self::cleanImageUrl($matches[2]); return str_replace($matches[2], $cleanUrl, $matches[0]); }, $html ); } private static function createImageTag($src, $attributes) { $html = ' $value) { $html .= ' ' . $key . '="' . htmlspecialchars($value) . '"'; } $html .= '>'; return $html; } }