\/\*[\s\S]*?\*\/)' . '|(?"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\')' . '|(?@[\w-]+|!important)' . '|(?[\w-]+(?=\s*\())' . '|(?[A-Za-z-]+(?=\s*:))' . '|(?#[0-9A-Fa-f]{3,8}\b|\b\d+(?:\.\d+)?(?:px|em|rem|ex|ch|vw|vh|vmin|vmax|%|pt|cm|mm|in|deg|s|ms|fr)?\b)' . '|(?[.#][A-Za-z_][\w-]*|::?[A-Za-z-]+)/'; } if ($lang === 'html') { return '/(?)' . '|(?"(?:\\\\.|[^"\\\\])*"|\'(?:\\\\.|[^\'\\\\])*\')' . '|(?<\/?[A-Za-z][\w:-]*|\/?>)' . '|(?\s[A-Za-z_:][\w:.-]*(?=\s*=))/'; } if ($lang === 'json') { return '/(?"(?:\\\\.|[^"\\\\])*"(?=\s*:))' . '|(?"(?:\\\\.|[^"\\\\])*")' . '|(?\b(?:true|false|null)\b)' . '|(?-?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)/'; } if ($lang === 'markdown') { return '/(?^\s{0,3}#{1,6}\s.*$|^\s*[-*+]\s|^\s*>\s)' . '|(?`[^`]+`)' . '|(?\*\*[^*]+\*\*|__[^_]+__)' . '|(?\[[^\]]+\]\([^)]+\))/m'; } // --- Keyword-based languages share one builder. --- $keywords = highlight_keywords($lang); if (!$keywords) { return null; } $comment = highlight_comment($lang); // String styles (single, double; backticks for js/bash). $string = '"(?:\\\\.|[^"\\\\])*"' . '|' . "'(?:\\\\.|[^'\\\\])*'"; if ($lang === 'javascript' || $lang === 'bash') { $string .= '|`(?:\\\\.|[^`\\\\])*`'; } $number = '\b\d+(?:\.\d+)?\b'; if ($lang === 'php' || $lang === 'bash') { $variable = '\$\{[^}]+\}|\$[A-Za-z_]\w*'; } else { $variable = '(?!)'; // never matches } $kw = '\b(?:' . implode('|', array_map('preg_quote', $keywords)) . ')\b'; $func = '[A-Za-z_]\w*(?=\s*\()'; $flags = ($lang === 'sql') ? 'i' : ''; return '/(?' . $comment . ')' . '|(?' . $string . ')' . '|(?' . $number . ')' . '|(?' . $variable . ')' . '|(?' . $kw . ')' . '|(?' . $func . ')/' . $flags; } /** * Return HTML for the given source code with token s. The result is safe * to embed directly (all literal text is HTML-escaped). */ function highlight_code(string $code, string $lang): string { $lang = strtolower($lang); $regex = highlight_regex($lang); // Unknown language: escape and return as-is. if ($regex === null) { return htmlspecialchars($code, ENT_QUOTES, 'UTF-8'); } // Every group name that may appear in any language's regex. $groups = ['com', 'str', 'num', 'var', 'kw', 'fn', 'tag', 'attr', 'sel']; $out = ''; $offset = 0; if (preg_match_all($regex, $code, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { foreach ($matches as $m) { $full = $m[0][0]; $pos = $m[0][1]; if ($pos > $offset) { $out .= htmlspecialchars(substr($code, $offset, $pos - $offset), ENT_QUOTES, 'UTF-8'); } $class = null; foreach ($groups as $g) { if (isset($m[$g]) && $m[$g][1] !== -1 && $m[$g][0] !== '') { $class = $g; break; } } // Preserve any leading whitespace captured by a token (e.g. HTML // attributes, markdown list markers) outside the coloured span. $lead = ''; if ($class !== null && ($class === 'attr' || $class === 'tag') && preg_match('/^\s+/', $full, $ws)) { $lead = $ws[0]; $full = substr($full, strlen($lead)); } $esc = htmlspecialchars($full, ENT_QUOTES, 'UTF-8'); $out .= $lead . ($class ? '' . $esc . '' : $esc); $offset = $pos + strlen($m[0][0]); } } if ($offset < strlen($code)) { $out .= htmlspecialchars(substr($code, $offset), ENT_QUOTES, 'UTF-8'); } return $out; }