@kirigami/php-prepros 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/libraries/md.class.php +303 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirigami/php-prepros",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "PHP preprocessor for the Kirigami static site generator. Compile PHP page templates to clean, deployable HTML — with zero server dependency.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kirigami",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@kirigami/php-wasm": "8.5.9",
|
|
48
|
-
"@kirigami/struct-walker": "1.0.
|
|
47
|
+
"@kirigami/php-wasm": "8.5.9-1",
|
|
48
|
+
"@kirigami/struct-walker": "1.0.3"
|
|
49
49
|
},
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|
|
@@ -107,6 +107,174 @@ class MD {
|
|
|
107
107
|
return $out . "</{$type}>";
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// ========================================================================
|
|
111
|
+
// ÉMOJIS (syntaxe étendue) : :shortcode: → caractère unicode.
|
|
112
|
+
// Table non exhaustive mais couvrant les raccourcis les plus courants ;
|
|
113
|
+
// extensible via registerEmoji().
|
|
114
|
+
// ========================================================================
|
|
115
|
+
/** @var array<string, string> */
|
|
116
|
+
private static array $extraEmoji = [];
|
|
117
|
+
|
|
118
|
+
private static array $emojiMap = [
|
|
119
|
+
'smile' => '😄', 'smiley' => '😃', 'grin' => '😁', 'joy' => '😂', 'rofl' => '🤣',
|
|
120
|
+
'blush' => '😊', 'wink' => '😉', 'relaxed' => '☺️', 'slight_smile' => '🙂',
|
|
121
|
+
'upside_down_face' => '🙃', 'innocent' => '😇', 'heart_eyes' => '😍', 'kissing_heart' => '😘',
|
|
122
|
+
'thinking' => '🤔', 'neutral_face' => '😐', 'expressionless' => '😑', 'no_mouth' => '😶',
|
|
123
|
+
'roll_eyes' => '🙄', 'smirk' => '😏', 'unamused' => '😒', 'grimacing' => '😬',
|
|
124
|
+
'lying_face' => '🤥', 'relieved' => '😌', 'pensive' => '😔', 'sleepy' => '😪',
|
|
125
|
+
'drooling_face' => '🤤', 'sleeping' => '😴', 'mask' => '😷', 'sunglasses' => '😎',
|
|
126
|
+
'star_struck' => '🤩', 'partying_face' => '🥳', 'worried' => '😟', 'frowning' => '☹️',
|
|
127
|
+
'confused' => '😕', 'slightly_frowning_face' => '🙁', 'cry' => '😢', 'sob' => '😭',
|
|
128
|
+
'scream' => '😱', 'confounded' => '😖', 'persevere' => '😣', 'disappointed' => '😞',
|
|
129
|
+
'sweat' => '😓', 'weary' => '😩', 'tired_face' => '😫', 'yawning_face' => '🥱',
|
|
130
|
+
'triumph' => '😤', 'rage' => '😡', 'angry' => '😠', 'cursing_face' => '🤬',
|
|
131
|
+
'exploding_head' => '🤯', 'flushed' => '😳', 'hot_face' => '🥵', 'cold_face' => '🥶',
|
|
132
|
+
'scream_cat' => '🙀', 'nerd_face' => '🤓', 'monocle_face' => '🧐', 'zany_face' => '🤪',
|
|
133
|
+
'raised_eyebrow' => '🤨', 'shushing_face' => '🤫', 'zipper_mouth_face' => '🤐',
|
|
134
|
+
'heart' => '❤️', 'orange_heart' => '🧡', 'yellow_heart' => '💛', 'green_heart' => '💚',
|
|
135
|
+
'blue_heart' => '💙', 'purple_heart' => '💜', 'black_heart' => '🖤', 'white_heart' => '🤍',
|
|
136
|
+
'broken_heart' => '💔', 'two_hearts' => '💕', 'sparkling_heart' => '💖', 'heartbeat' => '💓',
|
|
137
|
+
'thumbsup' => '👍', '+1' => '👍', 'thumbsdown' => '👎', '-1' => '👎',
|
|
138
|
+
'clap' => '👏', 'raised_hands' => '🙌', 'pray' => '🙏', 'wave' => '👋',
|
|
139
|
+
'ok_hand' => '👌', 'v' => '✌️', 'crossed_fingers' => '🤞', 'muscle' => '💪',
|
|
140
|
+
'point_up' => '☝️', 'point_down' => '👇', 'point_left' => '👈', 'point_right' => '👉',
|
|
141
|
+
'handshake' => '🤝', 'writing_hand' => '✍️', 'fire' => '🔥', 'star' => '⭐',
|
|
142
|
+
'star2' => '🌟', 'sparkles' => '✨', 'zap' => '⚡', 'boom' => '💥', 'collision' => '💥',
|
|
143
|
+
'rocket' => '🚀', 'tada' => '🎉', 'confetti_ball' => '🎊', 'gift' => '🎁',
|
|
144
|
+
'balloon' => '🎈', 'trophy' => '🏆', 'medal' => '🏅', 'crown' => '👑',
|
|
145
|
+
'gem' => '💎', 'moneybag' => '💰', 'dollar' => '💵', '100' => '💯',
|
|
146
|
+
'warning' => '⚠️', 'no_entry' => '⛔', 'stop_sign' => '🛑', 'checkered_flag' => '🏁',
|
|
147
|
+
'white_check_mark' => '✅', 'heavy_check_mark' => '✔️', 'x' => '❌', 'negative_squared_cross_mark' => '❎',
|
|
148
|
+
'question' => '❓', 'grey_question' => '❔', 'exclamation' => '❗', 'bangbang' => '‼️',
|
|
149
|
+
'interrobang' => '⁉️', 'bulb' => '💡', 'bell' => '🔔', 'no_bell' => '🔕',
|
|
150
|
+
'lock' => '🔒', 'unlock' => '🔓', 'key' => '🔑', 'mag' => '🔍', 'link' => '🔗',
|
|
151
|
+
'pushpin' => '📌', 'paperclip' => '📎', 'calendar' => '📅', 'clock' => '🕐',
|
|
152
|
+
'hourglass' => '⌛', 'alarm_clock' => '⏰', 'memo' => '📝', 'pencil2' => '✏️',
|
|
153
|
+
'book' => '📖', 'books' => '📚', 'newspaper' => '📰', 'email' => '📧',
|
|
154
|
+
'envelope' => '✉️', 'inbox_tray' => '📥', 'outbox_tray' => '📤', 'package' => '📦',
|
|
155
|
+
'file_folder' => '📁', 'open_file_folder' => '📂', 'clipboard' => '📋',
|
|
156
|
+
'chart_with_upwards_trend' => '📈', 'chart_with_downwards_trend' => '📉', 'bar_chart' => '📊',
|
|
157
|
+
'computer' => '💻', 'desktop_computer' => '🖥️', 'keyboard' => '⌨️', 'printer' => '🖨️',
|
|
158
|
+
'phone' => '📱', 'iphone' => '📱', 'camera' => '📷', 'video_camera' => '📹',
|
|
159
|
+
'tv' => '📺', 'radio' => '📻', 'battery' => '🔋', 'electric_plug' => '🔌',
|
|
160
|
+
'bug' => '🐛', 'beetle' => '🪲', 'gear' => '⚙️', 'wrench' => '🔧', 'hammer' => '🔨',
|
|
161
|
+
'nut_and_bolt' => '🔩', 'toolbox' => '🧰', 'test_tube' => '🧪', 'microscope' => '🔬',
|
|
162
|
+
'satellite' => '🛰️', 'globe_with_meridians' => '🌐', 'earth_americas' => '🌎',
|
|
163
|
+
'sun' => '☀️', 'sunny' => '☀️', 'partly_sunny' => '⛅', 'cloud' => '☁️',
|
|
164
|
+
'rainbow' => '🌈', 'umbrella' => '☂️', 'snowflake' => '❄️', 'droplet' => '💧',
|
|
165
|
+
'ocean' => '🌊', 'tent' => '⛺', 'camping' => '🏕️', 'mountain' => '⛰️',
|
|
166
|
+
'evergreen_tree' => '🌲', 'deciduous_tree' => '🌳', 'palm_tree' => '🌴',
|
|
167
|
+
'cactus' => '🌵', 'seedling' => '🌱', 'four_leaf_clover' => '🍀', 'maple_leaf' => '🍁',
|
|
168
|
+
'dog' => '🐶', 'cat' => '🐱', 'mouse' => '🐭', 'rabbit' => '🐰', 'fox_face' => '🦊',
|
|
169
|
+
'bear' => '🐻', 'panda_face' => '🐼', 'koala' => '🐨', 'tiger' => '🐯', 'lion' => '🦁',
|
|
170
|
+
'cow' => '🐮', 'pig' => '🐷', 'frog' => '🐸', 'monkey_face' => '🐵', 'chicken' => '🐔',
|
|
171
|
+
'penguin' => '🐧', 'bird' => '🐦', 'baby_chick' => '🐤', 'owl' => '🦉',
|
|
172
|
+
'horse' => '🐴', 'unicorn' => '🦄', 'bee' => '🐝', 'butterfly' => '🦋', 'snail' => '🐌',
|
|
173
|
+
'octopus' => '🐙', 'fish' => '🐟', 'dolphin' => '🐬', 'whale' => '🐳',
|
|
174
|
+
'pizza' => '🍕', 'hamburger' => '🍔', 'fries' => '🍟', 'hotdog' => '🌭',
|
|
175
|
+
'taco' => '🌮', 'sushi' => '🍣', 'ramen' => '🍜', 'spaghetti' => '🍝',
|
|
176
|
+
'bread' => '🍞', 'cheese' => '🧀', 'egg' => '🥚', 'popcorn' => '🍿',
|
|
177
|
+
'cookie' => '🍪', 'doughnut' => '🍩', 'cake' => '🍰', 'birthday' => '🎂',
|
|
178
|
+
'candy' => '🍬', 'chocolate_bar' => '🍫', 'icecream' => '🍦', 'apple' => '🍎',
|
|
179
|
+
'banana' => '🍌', 'grapes' => '🍇', 'watermelon' => '🍉', 'strawberry' => '🍓',
|
|
180
|
+
'lemon' => '🍋', 'peach' => '🍑', 'coffee' => '☕', 'tea' => '🍵', 'beer' => '🍺',
|
|
181
|
+
'beers' => '🍻', 'wine_glass' => '🍷', 'cocktail' => '🍸', 'tropical_drink' => '🍹',
|
|
182
|
+
'champagne' => '🍾', 'soccer' => '⚽', 'basketball' => '🏀', 'football' => '🏈',
|
|
183
|
+
'baseball' => '⚾', 'tennis' => '🎾', 'volleyball' => '🏐', 'rugby_football' => '🏉',
|
|
184
|
+
'8ball' => '🎱', 'golf' => '⛳', 'dart' => '🎯', 'video_game' => '🎮',
|
|
185
|
+
'game_die' => '🎲', 'jigsaw' => '🧩', 'car' => '🚗', 'taxi' => '🚕', 'bus' => '🚌',
|
|
186
|
+
'ambulance' => '🚑', 'fire_engine' => '🚒', 'police_car' => '🚓', 'bike' => '🚲',
|
|
187
|
+
'airplane' => '✈️', 'helicopter' => '🚁', 'train' => '🚆', 'ship' => '🚢',
|
|
188
|
+
'house' => '🏠', 'office' => '🏢', 'hospital' => '🏥', 'school' => '🏫',
|
|
189
|
+
'church' => '⛪', 'castle' => '🏰', 'world_map' => '🗺️', 'flag_white' => '🏳️',
|
|
190
|
+
'flag_black' => '🏴', 'checkered_flag2' => '🏁', 'eyes' => '👀', 'eye' => '👁️',
|
|
191
|
+
'speech_balloon' => '💬', 'thought_balloon' => '💭', 'zzz' => '💤', 'boom2' => '💥',
|
|
192
|
+
'sos' => '🆘', 'new' => '🆕', 'ok' => '🆗', 'up' => '🆙', 'cool' => '🆒',
|
|
193
|
+
'free' => '🆓', 'id' => '🆔', 'ng' => '🆖',
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Enregistre (ou remplace) un raccourci emoji personnalisé.
|
|
198
|
+
*/
|
|
199
|
+
public static function registerEmoji(string $shortcode, string $char): void {
|
|
200
|
+
self::$extraEmoji[strtolower(trim($shortcode, ':'))] = $char;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private static function emojiFor(string $shortcode): ?string {
|
|
204
|
+
$key = strtolower($shortcode);
|
|
205
|
+
return self::$extraEmoji[$key] ?? self::$emojiMap[$key] ?? null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ========================================================================
|
|
209
|
+
// LISTES DE DÉFINITION (syntaxe étendue)
|
|
210
|
+
// Terme
|
|
211
|
+
// : Définition
|
|
212
|
+
// Analyse procédurale ligne par ligne (plus sûre qu'une seule grosse
|
|
213
|
+
// regex pour regrouper plusieurs paires terme/définitions dans un même
|
|
214
|
+
// <dl>, séparées ou non par une ligne vide).
|
|
215
|
+
// ========================================================================
|
|
216
|
+
private static function isDefinitionColonLine(string $line): bool {
|
|
217
|
+
return (bool) preg_match('/^[ \t]*:[ \t]+.+$/', $line);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private static function looksLikeOtherBlock(string $line): bool {
|
|
221
|
+
$t = ltrim($line);
|
|
222
|
+
if ($t === '') return true;
|
|
223
|
+
if (str_starts_with($t, '<')) return true;
|
|
224
|
+
return (bool) preg_match('/^(?:#{1,6}[ \t]|>|```|\||[-*+][ \t]|\d+\.[ \t])/', $t);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private static function extractDefinitionLists(string $html): string {
|
|
228
|
+
$lines = explode("\n", $html);
|
|
229
|
+
$n = count($lines);
|
|
230
|
+
$out = [];
|
|
231
|
+
$i = 0;
|
|
232
|
+
|
|
233
|
+
while ($i < $n) {
|
|
234
|
+
$isTermStart = $i + 1 < $n
|
|
235
|
+
&& !self::looksLikeOtherBlock($lines[$i])
|
|
236
|
+
&& !self::isDefinitionColonLine($lines[$i])
|
|
237
|
+
&& self::isDefinitionColonLine($lines[$i + 1]);
|
|
238
|
+
|
|
239
|
+
if (!$isTermStart) {
|
|
240
|
+
$out[] = $lines[$i];
|
|
241
|
+
$i++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
$dl = "<dl>\n";
|
|
246
|
+
while (true) {
|
|
247
|
+
$term = trim($lines[$i]);
|
|
248
|
+
$dl .= " <dt>{$term}</dt>\n";
|
|
249
|
+
$i++;
|
|
250
|
+
while ($i < $n && preg_match('/^[ \t]*:[ \t]+(.*)$/', $lines[$i], $m)) {
|
|
251
|
+
$dl .= " <dd>{$m[1]}</dd>\n";
|
|
252
|
+
$i++;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Une seule ligne vide entre deux groupes reste dans le même <dl>
|
|
256
|
+
// si le groupe suivant est bien un nouveau terme.
|
|
257
|
+
if ($i < $n && trim($lines[$i]) === '') {
|
|
258
|
+
$j = $i;
|
|
259
|
+
while ($j < $n && trim($lines[$j]) === '') $j++;
|
|
260
|
+
if ($j + 1 < $n
|
|
261
|
+
&& !self::looksLikeOtherBlock($lines[$j])
|
|
262
|
+
&& !self::isDefinitionColonLine($lines[$j])
|
|
263
|
+
&& self::isDefinitionColonLine($lines[$j + 1])
|
|
264
|
+
) {
|
|
265
|
+
$i = $j;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
$dl .= "</dl>";
|
|
272
|
+
$out[] = $dl;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return implode("\n", $out);
|
|
276
|
+
}
|
|
277
|
+
|
|
110
278
|
// ========================================================================
|
|
111
279
|
|
|
112
280
|
public static function toHtml(string $markdown): string {
|
|
@@ -180,6 +348,38 @@ class MD {
|
|
|
180
348
|
);
|
|
181
349
|
|
|
182
350
|
|
|
351
|
+
// ====================================================================
|
|
352
|
+
// ÉTAPE 2a : DÉFINITIONS DE NOTES DE BAS DE PAGE (footnotes)
|
|
353
|
+
// [^1]: Texte de la note.
|
|
354
|
+
// [^bignote]: Première ligne.
|
|
355
|
+
//
|
|
356
|
+
// Paragraphe suivant, indenté de 4 espaces ou 1 tabulation.
|
|
357
|
+
//
|
|
358
|
+
// `{ du code }`
|
|
359
|
+
// Extraites (et retirées du texte) AVANT les définitions de liens par
|
|
360
|
+
// référence, car [^label]: matcherait aussi leur regex sinon.
|
|
361
|
+
// Le contenu de chaque note est rendu via un appel récursif à
|
|
362
|
+
// toHtml() pour supporter plusieurs paragraphes, du code, etc.
|
|
363
|
+
// ====================================================================
|
|
364
|
+
$footnoteDefs = [];
|
|
365
|
+
$html = preg_replace_callback(
|
|
366
|
+
'/^\[\^([^\]\s]+)\]:[ \t]?([^\n]*)((?:\n(?:[ \t]{4}[^\n]*|[ \t]*))*)/m',
|
|
367
|
+
function ($m) use (&$footnoteDefs): string {
|
|
368
|
+
$label = strtolower(trim($m[1]));
|
|
369
|
+
$first = $m[2];
|
|
370
|
+
$rest = $m[3] ?? '';
|
|
371
|
+
$restLines = $rest !== '' ? explode("\n", $rest) : [];
|
|
372
|
+
$restLines = array_map(static function (string $l): string {
|
|
373
|
+
return preg_replace('/^(?:[ ]{4}|\t)/', '', $l);
|
|
374
|
+
}, $restLines);
|
|
375
|
+
$content = trim($first . "\n" . implode("\n", $restLines));
|
|
376
|
+
$footnoteDefs[$label] = self::toHtml($content);
|
|
377
|
+
return '';
|
|
378
|
+
},
|
|
379
|
+
$html
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
|
|
183
383
|
// ====================================================================
|
|
184
384
|
// ÉTAPE 2b : DÉFINITIONS DE LIENS PAR RÉFÉRENCE
|
|
185
385
|
// [label]: https://example.com "Titre optionnel"
|
|
@@ -275,6 +475,18 @@ class MD {
|
|
|
275
475
|
},
|
|
276
476
|
$html
|
|
277
477
|
);
|
|
478
|
+
// | est la convention documentée (Markdown Extra / PHP Markdown)
|
|
479
|
+
// pour afficher un pipe littéral dans une cellule de tableau sans
|
|
480
|
+
// qu'il soit interprété comme séparateur de colonnes.
|
|
481
|
+
$html = preg_replace_callback(
|
|
482
|
+
'/|/i',
|
|
483
|
+
function () use (&$escapes): string {
|
|
484
|
+
$placeholder = "\x02ESC" . count($escapes) . "\x03";
|
|
485
|
+
$escapes[$placeholder] = '|';
|
|
486
|
+
return $placeholder;
|
|
487
|
+
},
|
|
488
|
+
$html
|
|
489
|
+
);
|
|
278
490
|
|
|
279
491
|
|
|
280
492
|
// ====================================================================
|
|
@@ -418,19 +630,19 @@ class MD {
|
|
|
418
630
|
// ligne de tirets juste après une ligne de texte est un titre, pas un <hr>).
|
|
419
631
|
// ====================================================================
|
|
420
632
|
$html = preg_replace_callback(
|
|
421
|
-
'/^(?![ \t]*(?:#{1,6}[ \t]|>|```|\||[-*+][ \t]|\d+\.[ \t]))[ \t]*(\S.*?)[ \t]
|
|
633
|
+
'/^(?![ \t]*(?:#{1,6}[ \t]|>|```|\||[-*+][ \t]|\d+\.[ \t]))[ \t]*(\S.*?)[ \t]*(?:\{#([a-zA-Z0-9_\-:.]+)\}[ \t]*)?\n[ \t]*=+[ \t]*$/m',
|
|
422
634
|
function ($matches) {
|
|
423
635
|
$text = trim($matches[1]);
|
|
424
|
-
$id = self::slugify($text);
|
|
636
|
+
$id = !empty($matches[2]) ? $matches[2] : self::slugify($text);
|
|
425
637
|
return "<h1 id=\"{$id}\">{$text}</h1>";
|
|
426
638
|
},
|
|
427
639
|
$html
|
|
428
640
|
);
|
|
429
641
|
$html = preg_replace_callback(
|
|
430
|
-
'/^(?![ \t]*(?:#{1,6}[ \t]|>|```|\||[-*+][ \t]|\d+\.[ \t]))[ \t]*(\S.*?)[ \t]
|
|
642
|
+
'/^(?![ \t]*(?:#{1,6}[ \t]|>|```|\||[-*+][ \t]|\d+\.[ \t]))[ \t]*(\S.*?)[ \t]*(?:\{#([a-zA-Z0-9_\-:.]+)\}[ \t]*)?\n[ \t]*-+[ \t]*$/m',
|
|
431
643
|
function ($matches) {
|
|
432
644
|
$text = trim($matches[1]);
|
|
433
|
-
$id = self::slugify($text);
|
|
645
|
+
$id = !empty($matches[2]) ? $matches[2] : self::slugify($text);
|
|
434
646
|
return "<h2 id=\"{$id}\">{$text}</h2>";
|
|
435
647
|
},
|
|
436
648
|
$html
|
|
@@ -440,12 +652,16 @@ class MD {
|
|
|
440
652
|
// ====================================================================
|
|
441
653
|
// ÉTAPE 8 : TITRES (ATX : # à ######)
|
|
442
654
|
// ====================================================================
|
|
443
|
-
$html = preg_replace_callback(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
655
|
+
$html = preg_replace_callback(
|
|
656
|
+
'/^(#{1,6})[ \t]+(.+?)[ \t]*(?:\{#([a-zA-Z0-9_\-:.]+)\}[ \t]*)?(?:[ \t]+#+)?$/m',
|
|
657
|
+
function ($matches) {
|
|
658
|
+
$level = strlen($matches[1]);
|
|
659
|
+
$text = trim($matches[2]);
|
|
660
|
+
$id = !empty($matches[3]) ? $matches[3] : self::slugify($text);
|
|
661
|
+
return "<h{$level} id=\"{$id}\">{$text}</h{$level}>";
|
|
662
|
+
},
|
|
663
|
+
$html
|
|
664
|
+
);
|
|
449
665
|
|
|
450
666
|
|
|
451
667
|
// ====================================================================
|
|
@@ -491,7 +707,39 @@ class MD {
|
|
|
491
707
|
|
|
492
708
|
|
|
493
709
|
// ====================================================================
|
|
494
|
-
// ÉTAPE
|
|
710
|
+
// ÉTAPE 9b : LISTES DE DÉFINITION (syntaxe étendue)
|
|
711
|
+
// Terme
|
|
712
|
+
// : Définition
|
|
713
|
+
// ====================================================================
|
|
714
|
+
$html = self::extractDefinitionLists($html);
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
// ====================================================================
|
|
718
|
+
// ÉTAPE 9c : RÉFÉRENCES DE NOTES DE BAS DE PAGE [^label]
|
|
719
|
+
// Converties AVANT l'emphase pour ne pas entrer en collision avec le
|
|
720
|
+
// nouvel exposant ^texte^ (un [^1] suivi plus loin d'un [^2] sur la
|
|
721
|
+
// même ligne pourrait sinon être interprété comme ^1] ... [^2^).
|
|
722
|
+
// La numérotation est séquentielle, dans l'ordre de première
|
|
723
|
+
// apparition dans le texte (comme documenté).
|
|
724
|
+
// ====================================================================
|
|
725
|
+
$footnoteOrder = [];
|
|
726
|
+
$html = preg_replace_callback('/\[\^([^\]\s]+)\]/', function ($m) use (&$footnoteOrder, &$footnoteDefs): string {
|
|
727
|
+
$label = strtolower(trim($m[1]));
|
|
728
|
+
if (!isset($footnoteDefs[$label])) {
|
|
729
|
+
// Référence vers une note non définie : laissée telle quelle.
|
|
730
|
+
return $m[0];
|
|
731
|
+
}
|
|
732
|
+
if (!isset($footnoteOrder[$label])) {
|
|
733
|
+
$footnoteOrder[$label] = count($footnoteOrder) + 1;
|
|
734
|
+
}
|
|
735
|
+
$num = $footnoteOrder[$label];
|
|
736
|
+
return "<sup id=\"fnref:{$label}\"><a href=\"#fn:{$label}\">{$num}</a></sup>";
|
|
737
|
+
}, $html);
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
// ====================================================================
|
|
741
|
+
// ÉTAPE 10 : TEXTE EN LIGNE (Gras, Italique, Barré, Surlignage,
|
|
742
|
+
// Indice/Exposant, Emoji)
|
|
495
743
|
// ====================================================================
|
|
496
744
|
$html = preg_replace('/\*\*\*(.+?)\*\*\*/s', '<strong><em>$1</em></strong>', $html);
|
|
497
745
|
$html = preg_replace('/___(.+?)___/s', '<strong><em>$1</em></strong>', $html);
|
|
@@ -501,7 +749,25 @@ class MD {
|
|
|
501
749
|
// Le _ italique ne doit matcher qu'aux frontières de mots pour ne pas
|
|
502
750
|
// capturer les snake_case, noms de packages (@php-wasm/node), etc.
|
|
503
751
|
$html = preg_replace('/(?<!\w)_([^_\n]+)_(?!\w)/', '<em>$1</em>', $html);
|
|
752
|
+
// Surlignage ==texte== (syntaxe étendue)
|
|
753
|
+
$html = preg_replace('/==(.+?)==/s', '<mark>$1</mark>', $html);
|
|
754
|
+
// Barré ~~texte~~ — traité AVANT le sous-script (simple ~) pour que
|
|
755
|
+
// celui-ci ne matche pas la moitié d'une paire de tildes doubles.
|
|
504
756
|
$html = preg_replace('/~~(.+?)~~/s', '<del>$1</del>', $html);
|
|
757
|
+
// Exposant ^texte^ (syntaxe étendue) — placé avant l'échappement des
|
|
758
|
+
// références de notes ([^label]) n'est pas un souci : celles-ci sont
|
|
759
|
+
// encadrées de crochets et ne forment donc pas de paire ^...^ isolée.
|
|
760
|
+
$html = preg_replace('/\^([^\^\n]+)\^/', '<sup>$1</sup>', $html);
|
|
761
|
+
// Sous-script ~texte~ (un seul tilde ; les ~~ ont déjà été consommés
|
|
762
|
+
// juste au-dessus par le barré).
|
|
763
|
+
$html = preg_replace('/~([^~\n]+)~/', '<sub>$1</sub>', $html);
|
|
764
|
+
|
|
765
|
+
// Émojis :shortcode: (syntaxe étendue) — les raccourcis inconnus sont
|
|
766
|
+
// laissés tels quels plutôt que silencieusement supprimés.
|
|
767
|
+
$html = preg_replace_callback('/:([a-zA-Z0-9_+\-]+):/', function ($m): string {
|
|
768
|
+
$emoji = self::emojiFor($m[1]);
|
|
769
|
+
return $emoji ?? $m[0];
|
|
770
|
+
}, $html);
|
|
505
771
|
|
|
506
772
|
|
|
507
773
|
// ====================================================================
|
|
@@ -545,6 +811,15 @@ class MD {
|
|
|
545
811
|
$html
|
|
546
812
|
);
|
|
547
813
|
|
|
814
|
+
// URL nues https://... (syntaxe étendue : auto-link sans crochets).
|
|
815
|
+
// Exclut celles déjà entre guillemets/attributs (href="...") ou déjà
|
|
816
|
+
// transformées en lien pour ne pas les doubler.
|
|
817
|
+
$html = preg_replace(
|
|
818
|
+
'/(?<!["\'=>])\b(https?:\/\/[^\s<>"\')\]]+)/',
|
|
819
|
+
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
|
820
|
+
$html
|
|
821
|
+
);
|
|
822
|
+
|
|
548
823
|
|
|
549
824
|
// ====================================================================
|
|
550
825
|
// ÉTAPE 12 : LIGNES SÉPARATRICES
|
|
@@ -561,6 +836,7 @@ class MD {
|
|
|
561
836
|
// ====================================================================
|
|
562
837
|
$blockStartTags = ['<h', '<pre', '<ul', '<ol', '<li', '<table', '<thead', '<tbody',
|
|
563
838
|
'<tr', '<td', '<th', '<blockquote', '<div', '<hr', '<img',
|
|
839
|
+
'<dl', '<dt', '<dd',
|
|
564
840
|
"\x02CB", "\x02PLG", "\x02BQ"];
|
|
565
841
|
|
|
566
842
|
$isBlockLine = static function (string $line) use ($blockStartTags): bool {
|
|
@@ -622,6 +898,22 @@ class MD {
|
|
|
622
898
|
// aucune regex Markdown ne peut les interpréter.
|
|
623
899
|
$html = strtr($html, $escapes);
|
|
624
900
|
|
|
901
|
+
|
|
902
|
+
// ====================================================================
|
|
903
|
+
// ÉTAPE 15 : BLOC DES NOTES DE BAS DE PAGE
|
|
904
|
+
// Ajouté en fin de document, uniquement si au moins une note a été
|
|
905
|
+
// référencée (les notes définies mais jamais référencées sont
|
|
906
|
+
// silencieusement ignorées).
|
|
907
|
+
// ====================================================================
|
|
908
|
+
if (!empty($footnoteOrder)) {
|
|
909
|
+
$html .= "\n<div class=\"footnotes\">\n<ol>\n";
|
|
910
|
+
foreach ($footnoteOrder as $label => $num) {
|
|
911
|
+
$content = $footnoteDefs[$label];
|
|
912
|
+
$html .= " <li id=\"fn:{$label}\">{$content} <a href=\"#fnref:{$label}\" class=\"footnote-backref\">↩</a></li>\n";
|
|
913
|
+
}
|
|
914
|
+
$html .= "</ol>\n</div>";
|
|
915
|
+
}
|
|
916
|
+
|
|
625
917
|
return $html;
|
|
626
918
|
}
|
|
627
919
|
}
|