Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Helper; 4 5 use DOMDocument; 6 use DOMElement; 7 use DOMNode; 8 use DOMText; 9 use PhpOffice\PhpSpreadsheet\RichText\RichText; 10 use PhpOffice\PhpSpreadsheet\Style\Color; 11 use PhpOffice\PhpSpreadsheet\Style\Font; 12 13 class Html 14 { 15 private const COLOUR_MAP = [ 16 'aliceblue' => 'f0f8ff', 17 'antiquewhite' => 'faebd7', 18 'antiquewhite1' => 'ffefdb', 19 'antiquewhite2' => 'eedfcc', 20 'antiquewhite3' => 'cdc0b0', 21 'antiquewhite4' => '8b8378', 22 'aqua' => '00ffff', 23 'aquamarine1' => '7fffd4', 24 'aquamarine2' => '76eec6', 25 'aquamarine4' => '458b74', 26 'azure1' => 'f0ffff', 27 'azure2' => 'e0eeee', 28 'azure3' => 'c1cdcd', 29 'azure4' => '838b8b', 30 'beige' => 'f5f5dc', 31 'bisque1' => 'ffe4c4', 32 'bisque2' => 'eed5b7', 33 'bisque3' => 'cdb79e', 34 'bisque4' => '8b7d6b', 35 'black' => '000000', 36 'blanchedalmond' => 'ffebcd', 37 'blue' => '0000ff', 38 'blue1' => '0000ff', 39 'blue2' => '0000ee', 40 'blue4' => '00008b', 41 'blueviolet' => '8a2be2', 42 'brown' => 'a52a2a', 43 'brown1' => 'ff4040', 44 'brown2' => 'ee3b3b', 45 'brown3' => 'cd3333', 46 'brown4' => '8b2323', 47 'burlywood' => 'deb887', 48 'burlywood1' => 'ffd39b', 49 'burlywood2' => 'eec591', 50 'burlywood3' => 'cdaa7d', 51 'burlywood4' => '8b7355', 52 'cadetblue' => '5f9ea0', 53 'cadetblue1' => '98f5ff', 54 'cadetblue2' => '8ee5ee', 55 'cadetblue3' => '7ac5cd', 56 'cadetblue4' => '53868b', 57 'chartreuse1' => '7fff00', 58 'chartreuse2' => '76ee00', 59 'chartreuse3' => '66cd00', 60 'chartreuse4' => '458b00', 61 'chocolate' => 'd2691e', 62 'chocolate1' => 'ff7f24', 63 'chocolate2' => 'ee7621', 64 'chocolate3' => 'cd661d', 65 'coral' => 'ff7f50', 66 'coral1' => 'ff7256', 67 'coral2' => 'ee6a50', 68 'coral3' => 'cd5b45', 69 'coral4' => '8b3e2f', 70 'cornflowerblue' => '6495ed', 71 'cornsilk1' => 'fff8dc', 72 'cornsilk2' => 'eee8cd', 73 'cornsilk3' => 'cdc8b1', 74 'cornsilk4' => '8b8878', 75 'cyan1' => '00ffff', 76 'cyan2' => '00eeee', 77 'cyan3' => '00cdcd', 78 'cyan4' => '008b8b', 79 'darkgoldenrod' => 'b8860b', 80 'darkgoldenrod1' => 'ffb90f', 81 'darkgoldenrod2' => 'eead0e', 82 'darkgoldenrod3' => 'cd950c', 83 'darkgoldenrod4' => '8b6508', 84 'darkgreen' => '006400', 85 'darkkhaki' => 'bdb76b', 86 'darkolivegreen' => '556b2f', 87 'darkolivegreen1' => 'caff70', 88 'darkolivegreen2' => 'bcee68', 89 'darkolivegreen3' => 'a2cd5a', 90 'darkolivegreen4' => '6e8b3d', 91 'darkorange' => 'ff8c00', 92 'darkorange1' => 'ff7f00', 93 'darkorange2' => 'ee7600', 94 'darkorange3' => 'cd6600', 95 'darkorange4' => '8b4500', 96 'darkorchid' => '9932cc', 97 'darkorchid1' => 'bf3eff', 98 'darkorchid2' => 'b23aee', 99 'darkorchid3' => '9a32cd', 100 'darkorchid4' => '68228b', 101 'darksalmon' => 'e9967a', 102 'darkseagreen' => '8fbc8f', 103 'darkseagreen1' => 'c1ffc1', 104 'darkseagreen2' => 'b4eeb4', 105 'darkseagreen3' => '9bcd9b', 106 'darkseagreen4' => '698b69', 107 'darkslateblue' => '483d8b', 108 'darkslategray' => '2f4f4f', 109 'darkslategray1' => '97ffff', 110 'darkslategray2' => '8deeee', 111 'darkslategray3' => '79cdcd', 112 'darkslategray4' => '528b8b', 113 'darkturquoise' => '00ced1', 114 'darkviolet' => '9400d3', 115 'deeppink1' => 'ff1493', 116 'deeppink2' => 'ee1289', 117 'deeppink3' => 'cd1076', 118 'deeppink4' => '8b0a50', 119 'deepskyblue1' => '00bfff', 120 'deepskyblue2' => '00b2ee', 121 'deepskyblue3' => '009acd', 122 'deepskyblue4' => '00688b', 123 'dimgray' => '696969', 124 'dodgerblue1' => '1e90ff', 125 'dodgerblue2' => '1c86ee', 126 'dodgerblue3' => '1874cd', 127 'dodgerblue4' => '104e8b', 128 'firebrick' => 'b22222', 129 'firebrick1' => 'ff3030', 130 'firebrick2' => 'ee2c2c', 131 'firebrick3' => 'cd2626', 132 'firebrick4' => '8b1a1a', 133 'floralwhite' => 'fffaf0', 134 'forestgreen' => '228b22', 135 'fuchsia' => 'ff00ff', 136 'gainsboro' => 'dcdcdc', 137 'ghostwhite' => 'f8f8ff', 138 'gold1' => 'ffd700', 139 'gold2' => 'eec900', 140 'gold3' => 'cdad00', 141 'gold4' => '8b7500', 142 'goldenrod' => 'daa520', 143 'goldenrod1' => 'ffc125', 144 'goldenrod2' => 'eeb422', 145 'goldenrod3' => 'cd9b1d', 146 'goldenrod4' => '8b6914', 147 'gray' => 'bebebe', 148 'gray1' => '030303', 149 'gray10' => '1a1a1a', 150 'gray11' => '1c1c1c', 151 'gray12' => '1f1f1f', 152 'gray13' => '212121', 153 'gray14' => '242424', 154 'gray15' => '262626', 155 'gray16' => '292929', 156 'gray17' => '2b2b2b', 157 'gray18' => '2e2e2e', 158 'gray19' => '303030', 159 'gray2' => '050505', 160 'gray20' => '333333', 161 'gray21' => '363636', 162 'gray22' => '383838', 163 'gray23' => '3b3b3b', 164 'gray24' => '3d3d3d', 165 'gray25' => '404040', 166 'gray26' => '424242', 167 'gray27' => '454545', 168 'gray28' => '474747', 169 'gray29' => '4a4a4a', 170 'gray3' => '080808', 171 'gray30' => '4d4d4d', 172 'gray31' => '4f4f4f', 173 'gray32' => '525252', 174 'gray33' => '545454', 175 'gray34' => '575757', 176 'gray35' => '595959', 177 'gray36' => '5c5c5c', 178 'gray37' => '5e5e5e', 179 'gray38' => '616161', 180 'gray39' => '636363', 181 'gray4' => '0a0a0a', 182 'gray40' => '666666', 183 'gray41' => '696969', 184 'gray42' => '6b6b6b', 185 'gray43' => '6e6e6e', 186 'gray44' => '707070', 187 'gray45' => '737373', 188 'gray46' => '757575', 189 'gray47' => '787878', 190 'gray48' => '7a7a7a', 191 'gray49' => '7d7d7d', 192 'gray5' => '0d0d0d', 193 'gray50' => '7f7f7f', 194 'gray51' => '828282', 195 'gray52' => '858585', 196 'gray53' => '878787', 197 'gray54' => '8a8a8a', 198 'gray55' => '8c8c8c', 199 'gray56' => '8f8f8f', 200 'gray57' => '919191', 201 'gray58' => '949494', 202 'gray59' => '969696', 203 'gray6' => '0f0f0f', 204 'gray60' => '999999', 205 'gray61' => '9c9c9c', 206 'gray62' => '9e9e9e', 207 'gray63' => 'a1a1a1', 208 'gray64' => 'a3a3a3', 209 'gray65' => 'a6a6a6', 210 'gray66' => 'a8a8a8', 211 'gray67' => 'ababab', 212 'gray68' => 'adadad', 213 'gray69' => 'b0b0b0', 214 'gray7' => '121212', 215 'gray70' => 'b3b3b3', 216 'gray71' => 'b5b5b5', 217 'gray72' => 'b8b8b8', 218 'gray73' => 'bababa', 219 'gray74' => 'bdbdbd', 220 'gray75' => 'bfbfbf', 221 'gray76' => 'c2c2c2', 222 'gray77' => 'c4c4c4', 223 'gray78' => 'c7c7c7', 224 'gray79' => 'c9c9c9', 225 'gray8' => '141414', 226 'gray80' => 'cccccc', 227 'gray81' => 'cfcfcf', 228 'gray82' => 'd1d1d1', 229 'gray83' => 'd4d4d4', 230 'gray84' => 'd6d6d6', 231 'gray85' => 'd9d9d9', 232 'gray86' => 'dbdbdb', 233 'gray87' => 'dedede', 234 'gray88' => 'e0e0e0', 235 'gray89' => 'e3e3e3', 236 'gray9' => '171717', 237 'gray90' => 'e5e5e5', 238 'gray91' => 'e8e8e8', 239 'gray92' => 'ebebeb', 240 'gray93' => 'ededed', 241 'gray94' => 'f0f0f0', 242 'gray95' => 'f2f2f2', 243 'gray97' => 'f7f7f7', 244 'gray98' => 'fafafa', 245 'gray99' => 'fcfcfc', 246 'green' => '00ff00', 247 'green1' => '00ff00', 248 'green2' => '00ee00', 249 'green3' => '00cd00', 250 'green4' => '008b00', 251 'greenyellow' => 'adff2f', 252 'honeydew1' => 'f0fff0', 253 'honeydew2' => 'e0eee0', 254 'honeydew3' => 'c1cdc1', 255 'honeydew4' => '838b83', 256 'hotpink' => 'ff69b4', 257 'hotpink1' => 'ff6eb4', 258 'hotpink2' => 'ee6aa7', 259 'hotpink3' => 'cd6090', 260 'hotpink4' => '8b3a62', 261 'indianred' => 'cd5c5c', 262 'indianred1' => 'ff6a6a', 263 'indianred2' => 'ee6363', 264 'indianred3' => 'cd5555', 265 'indianred4' => '8b3a3a', 266 'ivory1' => 'fffff0', 267 'ivory2' => 'eeeee0', 268 'ivory3' => 'cdcdc1', 269 'ivory4' => '8b8b83', 270 'khaki' => 'f0e68c', 271 'khaki1' => 'fff68f', 272 'khaki2' => 'eee685', 273 'khaki3' => 'cdc673', 274 'khaki4' => '8b864e', 275 'lavender' => 'e6e6fa', 276 'lavenderblush1' => 'fff0f5', 277 'lavenderblush2' => 'eee0e5', 278 'lavenderblush3' => 'cdc1c5', 279 'lavenderblush4' => '8b8386', 280 'lawngreen' => '7cfc00', 281 'lemonchiffon1' => 'fffacd', 282 'lemonchiffon2' => 'eee9bf', 283 'lemonchiffon3' => 'cdc9a5', 284 'lemonchiffon4' => '8b8970', 285 'light' => 'eedd82', 286 'lightblue' => 'add8e6', 287 'lightblue1' => 'bfefff', 288 'lightblue2' => 'b2dfee', 289 'lightblue3' => '9ac0cd', 290 'lightblue4' => '68838b', 291 'lightcoral' => 'f08080', 292 'lightcyan1' => 'e0ffff', 293 'lightcyan2' => 'd1eeee', 294 'lightcyan3' => 'b4cdcd', 295 'lightcyan4' => '7a8b8b', 296 'lightgoldenrod1' => 'ffec8b', 297 'lightgoldenrod2' => 'eedc82', 298 'lightgoldenrod3' => 'cdbe70', 299 'lightgoldenrod4' => '8b814c', 300 'lightgoldenrodyellow' => 'fafad2', 301 'lightgray' => 'd3d3d3', 302 'lightpink' => 'ffb6c1', 303 'lightpink1' => 'ffaeb9', 304 'lightpink2' => 'eea2ad', 305 'lightpink3' => 'cd8c95', 306 'lightpink4' => '8b5f65', 307 'lightsalmon1' => 'ffa07a', 308 'lightsalmon2' => 'ee9572', 309 'lightsalmon3' => 'cd8162', 310 'lightsalmon4' => '8b5742', 311 'lightseagreen' => '20b2aa', 312 'lightskyblue' => '87cefa', 313 'lightskyblue1' => 'b0e2ff', 314 'lightskyblue2' => 'a4d3ee', 315 'lightskyblue3' => '8db6cd', 316 'lightskyblue4' => '607b8b', 317 'lightslateblue' => '8470ff', 318 'lightslategray' => '778899', 319 'lightsteelblue' => 'b0c4de', 320 'lightsteelblue1' => 'cae1ff', 321 'lightsteelblue2' => 'bcd2ee', 322 'lightsteelblue3' => 'a2b5cd', 323 'lightsteelblue4' => '6e7b8b', 324 'lightyellow1' => 'ffffe0', 325 'lightyellow2' => 'eeeed1', 326 'lightyellow3' => 'cdcdb4', 327 'lightyellow4' => '8b8b7a', 328 'lime' => '00ff00', 329 'limegreen' => '32cd32', 330 'linen' => 'faf0e6', 331 'magenta' => 'ff00ff', 332 'magenta2' => 'ee00ee', 333 'magenta3' => 'cd00cd', 334 'magenta4' => '8b008b', 335 'maroon' => 'b03060', 336 'maroon1' => 'ff34b3', 337 'maroon2' => 'ee30a7', 338 'maroon3' => 'cd2990', 339 'maroon4' => '8b1c62', 340 'medium' => '66cdaa', 341 'mediumaquamarine' => '66cdaa', 342 'mediumblue' => '0000cd', 343 'mediumorchid' => 'ba55d3', 344 'mediumorchid1' => 'e066ff', 345 'mediumorchid2' => 'd15fee', 346 'mediumorchid3' => 'b452cd', 347 'mediumorchid4' => '7a378b', 348 'mediumpurple' => '9370db', 349 'mediumpurple1' => 'ab82ff', 350 'mediumpurple2' => '9f79ee', 351 'mediumpurple3' => '8968cd', 352 'mediumpurple4' => '5d478b', 353 'mediumseagreen' => '3cb371', 354 'mediumslateblue' => '7b68ee', 355 'mediumspringgreen' => '00fa9a', 356 'mediumturquoise' => '48d1cc', 357 'mediumvioletred' => 'c71585', 358 'midnightblue' => '191970', 359 'mintcream' => 'f5fffa', 360 'mistyrose1' => 'ffe4e1', 361 'mistyrose2' => 'eed5d2', 362 'mistyrose3' => 'cdb7b5', 363 'mistyrose4' => '8b7d7b', 364 'moccasin' => 'ffe4b5', 365 'navajowhite1' => 'ffdead', 366 'navajowhite2' => 'eecfa1', 367 'navajowhite3' => 'cdb38b', 368 'navajowhite4' => '8b795e', 369 'navy' => '000080', 370 'navyblue' => '000080', 371 'oldlace' => 'fdf5e6', 372 'olive' => '808000', 373 'olivedrab' => '6b8e23', 374 'olivedrab1' => 'c0ff3e', 375 'olivedrab2' => 'b3ee3a', 376 'olivedrab4' => '698b22', 377 'orange' => 'ffa500', 378 'orange1' => 'ffa500', 379 'orange2' => 'ee9a00', 380 'orange3' => 'cd8500', 381 'orange4' => '8b5a00', 382 'orangered1' => 'ff4500', 383 'orangered2' => 'ee4000', 384 'orangered3' => 'cd3700', 385 'orangered4' => '8b2500', 386 'orchid' => 'da70d6', 387 'orchid1' => 'ff83fa', 388 'orchid2' => 'ee7ae9', 389 'orchid3' => 'cd69c9', 390 'orchid4' => '8b4789', 391 'pale' => 'db7093', 392 'palegoldenrod' => 'eee8aa', 393 'palegreen' => '98fb98', 394 'palegreen1' => '9aff9a', 395 'palegreen2' => '90ee90', 396 'palegreen3' => '7ccd7c', 397 'palegreen4' => '548b54', 398 'paleturquoise' => 'afeeee', 399 'paleturquoise1' => 'bbffff', 400 'paleturquoise2' => 'aeeeee', 401 'paleturquoise3' => '96cdcd', 402 'paleturquoise4' => '668b8b', 403 'palevioletred' => 'db7093', 404 'palevioletred1' => 'ff82ab', 405 'palevioletred2' => 'ee799f', 406 'palevioletred3' => 'cd6889', 407 'palevioletred4' => '8b475d', 408 'papayawhip' => 'ffefd5', 409 'peachpuff1' => 'ffdab9', 410 'peachpuff2' => 'eecbad', 411 'peachpuff3' => 'cdaf95', 412 'peachpuff4' => '8b7765', 413 'pink' => 'ffc0cb', 414 'pink1' => 'ffb5c5', 415 'pink2' => 'eea9b8', 416 'pink3' => 'cd919e', 417 'pink4' => '8b636c', 418 'plum' => 'dda0dd', 419 'plum1' => 'ffbbff', 420 'plum2' => 'eeaeee', 421 'plum3' => 'cd96cd', 422 'plum4' => '8b668b', 423 'powderblue' => 'b0e0e6', 424 'purple' => 'a020f0', 425 'rebeccapurple' => '663399', 426 'purple1' => '9b30ff', 427 'purple2' => '912cee', 428 'purple3' => '7d26cd', 429 'purple4' => '551a8b', 430 'red' => 'ff0000', 431 'red1' => 'ff0000', 432 'red2' => 'ee0000', 433 'red3' => 'cd0000', 434 'red4' => '8b0000', 435 'rosybrown' => 'bc8f8f', 436 'rosybrown1' => 'ffc1c1', 437 'rosybrown2' => 'eeb4b4', 438 'rosybrown3' => 'cd9b9b', 439 'rosybrown4' => '8b6969', 440 'royalblue' => '4169e1', 441 'royalblue1' => '4876ff', 442 'royalblue2' => '436eee', 443 'royalblue3' => '3a5fcd', 444 'royalblue4' => '27408b', 445 'saddlebrown' => '8b4513', 446 'salmon' => 'fa8072', 447 'salmon1' => 'ff8c69', 448 'salmon2' => 'ee8262', 449 'salmon3' => 'cd7054', 450 'salmon4' => '8b4c39', 451 'sandybrown' => 'f4a460', 452 'seagreen1' => '54ff9f', 453 'seagreen2' => '4eee94', 454 'seagreen3' => '43cd80', 455 'seagreen4' => '2e8b57', 456 'seashell1' => 'fff5ee', 457 'seashell2' => 'eee5de', 458 'seashell3' => 'cdc5bf', 459 'seashell4' => '8b8682', 460 'sienna' => 'a0522d', 461 'sienna1' => 'ff8247', 462 'sienna2' => 'ee7942', 463 'sienna3' => 'cd6839', 464 'sienna4' => '8b4726', 465 'silver' => 'c0c0c0', 466 'skyblue' => '87ceeb', 467 'skyblue1' => '87ceff', 468 'skyblue2' => '7ec0ee', 469 'skyblue3' => '6ca6cd', 470 'skyblue4' => '4a708b', 471 'slateblue' => '6a5acd', 472 'slateblue1' => '836fff', 473 'slateblue2' => '7a67ee', 474 'slateblue3' => '6959cd', 475 'slateblue4' => '473c8b', 476 'slategray' => '708090', 477 'slategray1' => 'c6e2ff', 478 'slategray2' => 'b9d3ee', 479 'slategray3' => '9fb6cd', 480 'slategray4' => '6c7b8b', 481 'snow1' => 'fffafa', 482 'snow2' => 'eee9e9', 483 'snow3' => 'cdc9c9', 484 'snow4' => '8b8989', 485 'springgreen1' => '00ff7f', 486 'springgreen2' => '00ee76', 487 'springgreen3' => '00cd66', 488 'springgreen4' => '008b45', 489 'steelblue' => '4682b4', 490 'steelblue1' => '63b8ff', 491 'steelblue2' => '5cacee', 492 'steelblue3' => '4f94cd', 493 'steelblue4' => '36648b', 494 'tan' => 'd2b48c', 495 'tan1' => 'ffa54f', 496 'tan2' => 'ee9a49', 497 'tan3' => 'cd853f', 498 'tan4' => '8b5a2b', 499 'teal' => '008080', 500 'thistle' => 'd8bfd8', 501 'thistle1' => 'ffe1ff', 502 'thistle2' => 'eed2ee', 503 'thistle3' => 'cdb5cd', 504 'thistle4' => '8b7b8b', 505 'tomato1' => 'ff6347', 506 'tomato2' => 'ee5c42', 507 'tomato3' => 'cd4f39', 508 'tomato4' => '8b3626', 509 'turquoise' => '40e0d0', 510 'turquoise1' => '00f5ff', 511 'turquoise2' => '00e5ee', 512 'turquoise3' => '00c5cd', 513 'turquoise4' => '00868b', 514 'violet' => 'ee82ee', 515 'violetred' => 'd02090', 516 'violetred1' => 'ff3e96', 517 'violetred2' => 'ee3a8c', 518 'violetred3' => 'cd3278', 519 'violetred4' => '8b2252', 520 'wheat' => 'f5deb3', 521 'wheat1' => 'ffe7ba', 522 'wheat2' => 'eed8ae', 523 'wheat3' => 'cdba96', 524 'wheat4' => '8b7e66', 525 'white' => 'ffffff', 526 'whitesmoke' => 'f5f5f5', 527 'yellow' => 'ffff00', 528 'yellow1' => 'ffff00', 529 'yellow2' => 'eeee00', 530 'yellow3' => 'cdcd00', 531 'yellow4' => '8b8b00', 532 'yellowgreen' => '9acd32', 533 ]; 534 535 /** @var ?string */ 536 private $face; 537 538 /** @var ?string */ 539 private $size; 540 541 /** @var ?string */ 542 private $color; 543 544 /** @var bool */ 545 private $bold = false; 546 547 /** @var bool */ 548 private $italic = false; 549 550 /** @var bool */ 551 private $underline = false; 552 553 /** @var bool */ 554 private $superscript = false; 555 556 /** @var bool */ 557 private $subscript = false; 558 559 /** @var bool */ 560 private $strikethrough = false; 561 562 private const START_TAG_CALLBACKS = [ 563 'font' => 'startFontTag', 564 'b' => 'startBoldTag', 565 'strong' => 'startBoldTag', 566 'i' => 'startItalicTag', 567 'em' => 'startItalicTag', 568 'u' => 'startUnderlineTag', 569 'ins' => 'startUnderlineTag', 570 'del' => 'startStrikethruTag', 571 'sup' => 'startSuperscriptTag', 572 'sub' => 'startSubscriptTag', 573 ]; 574 575 private const END_TAG_CALLBACKS = [ 576 'font' => 'endFontTag', 577 'b' => 'endBoldTag', 578 'strong' => 'endBoldTag', 579 'i' => 'endItalicTag', 580 'em' => 'endItalicTag', 581 'u' => 'endUnderlineTag', 582 'ins' => 'endUnderlineTag', 583 'del' => 'endStrikethruTag', 584 'sup' => 'endSuperscriptTag', 585 'sub' => 'endSubscriptTag', 586 'br' => 'breakTag', 587 'p' => 'breakTag', 588 'h1' => 'breakTag', 589 'h2' => 'breakTag', 590 'h3' => 'breakTag', 591 'h4' => 'breakTag', 592 'h5' => 'breakTag', 593 'h6' => 'breakTag', 594 ]; 595 596 /** @var array */ 597 private $stack = []; 598 599 /** @var string */ 600 private $stringData = ''; 601 602 /** 603 * @var RichText 604 */ 605 private $richTextObject; 606 607 private function initialise(): void 608 { 609 $this->face = $this->size = $this->color = null; 610 $this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false; 611 612 $this->stack = []; 613 614 $this->stringData = ''; 615 } 616 617 /** 618 * Parse HTML formatting and return the resulting RichText. 619 * 620 * @param string $html 621 * 622 * @return RichText 623 */ 624 public function toRichTextObject($html) 625 { 626 $this->initialise(); 627 628 // Create a new DOM object 629 $dom = new DOMDocument(); 630 // Load the HTML file into the DOM object 631 // Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup 632 $prefix = '<?xml encoding="UTF-8">'; 633 /** @scrutinizer ignore-unhandled */ 634 @$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); 635 // Discard excess white space 636 $dom->preserveWhiteSpace = false; 637 638 $this->richTextObject = new RichText(); 639 $this->parseElements($dom); 640 641 // Clean any further spurious whitespace 642 $this->cleanWhitespace(); 643 644 return $this->richTextObject; 645 } 646 647 private function cleanWhitespace(): void 648 { 649 foreach ($this->richTextObject->getRichTextElements() as $key => $element) { 650 $text = $element->getText(); 651 // Trim any leading spaces on the first run 652 if ($key == 0) { 653 $text = ltrim($text); 654 } 655 // Trim any spaces immediately after a line break 656 $text = (string) preg_replace('/\n */mu', "\n", $text); 657 $element->setText($text); 658 } 659 } 660 661 private function buildTextRun(): void 662 { 663 $text = $this->stringData; 664 if (trim($text) === '') { 665 return; 666 } 667 668 $richtextRun = $this->richTextObject->createTextRun($this->stringData); 669 $font = $richtextRun->getFont(); 670 if ($font !== null) { 671 if ($this->face) { 672 $font->setName($this->face); 673 } 674 if ($this->size) { 675 $font->setSize($this->size); 676 } 677 if ($this->color) { 678 $font->setColor(new Color('ff' . $this->color)); 679 } 680 if ($this->bold) { 681 $font->setBold(true); 682 } 683 if ($this->italic) { 684 $font->setItalic(true); 685 } 686 if ($this->underline) { 687 $font->setUnderline(Font::UNDERLINE_SINGLE); 688 } 689 if ($this->superscript) { 690 $font->setSuperscript(true); 691 } 692 if ($this->subscript) { 693 $font->setSubscript(true); 694 } 695 if ($this->strikethrough) { 696 $font->setStrikethrough(true); 697 } 698 } 699 $this->stringData = ''; 700 } 701 702 private function rgbToColour(string $rgbValue): string 703 { 704 preg_match_all('/\d+/', $rgbValue, $values); 705 foreach ($values[0] as &$value) { 706 $value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT); 707 } 708 709 return implode('', $values[0]); 710 } 711 712 public static function colourNameLookup(string $colorName): string 713 { 714 return self::COLOUR_MAP[$colorName] ?? ''; 715 } 716 717 private function startFontTag(DOMElement $tag): void 718 { 719 $attrs = $tag->attributes; 720 if ($attrs !== null) { 721 foreach ($attrs as $attribute) { 722 $attributeName = strtolower($attribute->name); 723 $attributeValue = $attribute->value; 724 725 if ($attributeName == 'color') { 726 if (preg_match('/rgb\s*\(/', $attributeValue)) { 727 $this->$attributeName = $this->rgbToColour($attributeValue); 728 } elseif (strpos(trim($attributeValue), '#') === 0) { 729 $this->$attributeName = ltrim($attributeValue, '#'); 730 } else { 731 $this->$attributeName = static::colourNameLookup($attributeValue); 732 } 733 } else { 734 $this->$attributeName = $attributeValue; 735 } 736 } 737 } 738 } 739 740 private function endFontTag(): void 741 { 742 $this->face = $this->size = $this->color = null; 743 } 744 745 private function startBoldTag(): void 746 { 747 $this->bold = true; 748 } 749 750 private function endBoldTag(): void 751 { 752 $this->bold = false; 753 } 754 755 private function startItalicTag(): void 756 { 757 $this->italic = true; 758 } 759 760 private function endItalicTag(): void 761 { 762 $this->italic = false; 763 } 764 765 private function startUnderlineTag(): void 766 { 767 $this->underline = true; 768 } 769 770 private function endUnderlineTag(): void 771 { 772 $this->underline = false; 773 } 774 775 private function startSubscriptTag(): void 776 { 777 $this->subscript = true; 778 } 779 780 private function endSubscriptTag(): void 781 { 782 $this->subscript = false; 783 } 784 785 private function startSuperscriptTag(): void 786 { 787 $this->superscript = true; 788 } 789 790 private function endSuperscriptTag(): void 791 { 792 $this->superscript = false; 793 } 794 795 private function startStrikethruTag(): void 796 { 797 $this->strikethrough = true; 798 } 799 800 private function endStrikethruTag(): void 801 { 802 $this->strikethrough = false; 803 } 804 805 private function breakTag(): void 806 { 807 $this->stringData .= "\n"; 808 } 809 810 private function parseTextNode(DOMText $textNode): void 811 { 812 $domText = (string) preg_replace( 813 '/\s+/u', 814 ' ', 815 str_replace(["\r", "\n"], ' ', $textNode->nodeValue ?? '') 816 ); 817 $this->stringData .= $domText; 818 $this->buildTextRun(); 819 } 820 821 /** 822 * @param string $callbackTag 823 */ 824 private function handleCallback(DOMElement $element, $callbackTag, array $callbacks): void 825 { 826 if (isset($callbacks[$callbackTag])) { 827 $elementHandler = $callbacks[$callbackTag]; 828 if (method_exists($this, $elementHandler)) { 829 /** @phpstan-ignore-next-line */ 830 call_user_func([$this, $elementHandler], $element); 831 } 832 } 833 } 834 835 private function parseElementNode(DOMElement $element): void 836 { 837 $callbackTag = strtolower($element->nodeName); 838 $this->stack[] = $callbackTag; 839 840 $this->handleCallback($element, $callbackTag, self::START_TAG_CALLBACKS); 841 842 $this->parseElements($element); 843 array_pop($this->stack); 844 845 $this->handleCallback($element, $callbackTag, self::END_TAG_CALLBACKS); 846 } 847 848 private function parseElements(DOMNode $element): void 849 { 850 foreach ($element->childNodes as $child) { 851 if ($child instanceof DOMText) { 852 $this->parseTextNode($child); 853 } elseif ($child instanceof DOMElement) { 854 $this->parseElementNode($child); 855 } 856 } 857 } 858 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body