From 1239936de9a4ce69f342d53090f588ec8fa5ffbc Mon Sep 17 00:00:00 2001 From: colinodell Date: Sun, 17 Nov 2024 00:08:23 +0000 Subject: [PATCH] jekyll build from Action c1012711cab1987f766a8005f20c45f9eed22b77 --- .nojekyll | 0 0.20/basic-usage/index.html | 51 + 0.20/changelog/index.html | 51 + 0.20/command-line/index.html | 51 + 0.20/configuration/index.html | 51 + .../abstract-syntax-tree/index.html | 51 + 0.20/customization/block-parsing/index.html | 51 + 0.20/customization/block-rendering/index.html | 51 + 0.20/customization/cursor/index.html | 51 + .../delimiter-processing/index.html | 51 + .../document-processing/index.html | 51 + 0.20/customization/environment/index.html | 51 + 0.20/customization/extensions/index.html | 51 + 0.20/customization/index.html | 51 + 0.20/customization/inline-parsing/index.html | 51 + .../customization/inline-rendering/index.html | 51 + 0.20/customization/overview/index.html | 51 + 0.20/index.html | 51 + 0.20/installation/index.html | 51 + 0.20/security/index.html | 51 + 0.20/upgrading/index.html | 51 + 1.0/basic-usage/index.html | 365 +++ 1.0/changelog/index.html | 623 ++++ 1.0/command-line/index.html | 357 ++ 1.0/configuration/index.html | 385 +++ .../abstract-syntax-tree/index.html | 365 +++ 1.0/customization/block-parsing/index.html | 426 +++ 1.0/customization/block-rendering/index.html | 412 +++ 1.0/customization/cursor/index.html | 448 +++ .../delimiter-processing/index.html | 402 +++ .../document-processing/index.html | 51 + 1.0/customization/environment/index.html | 409 +++ 1.0/customization/event-dispatcher/index.html | 456 +++ 1.0/customization/extensions/index.html | 460 +++ 1.0/customization/inline-parsing/index.html | 472 +++ 1.0/customization/inline-rendering/index.html | 412 +++ 1.0/customization/overview/index.html | 369 +++ 1.0/index.html | 352 ++ 1.0/installation/index.html | 329 ++ 1.0/security/index.html | 398 +++ 1.0/upgrading/index.html | 443 +++ 1.3/basic-usage/index.html | 426 +++ 1.3/changelog/index.html | 724 ++++ 1.3/command-line/index.html | 408 +++ 1.3/configuration/index.html | 430 +++ .../abstract-syntax-tree/index.html | 416 +++ 1.3/customization/block-parsing/index.html | 477 +++ 1.3/customization/block-rendering/index.html | 463 +++ 1.3/customization/cursor/index.html | 499 +++ .../delimiter-processing/index.html | 453 +++ .../document-processing/index.html | 51 + 1.3/customization/environment/index.html | 460 +++ 1.3/customization/event-dispatcher/index.html | 507 +++ 1.3/customization/extensions/index.html | 400 +++ 1.3/customization/inline-parsing/index.html | 523 +++ 1.3/customization/inline-rendering/index.html | 463 +++ 1.3/customization/overview/index.html | 420 +++ 1.3/extensions/autolinks/index.html | 448 +++ 1.3/extensions/commonmark/index.html | 428 +++ 1.3/extensions/disallowed-raw-html/index.html | 417 +++ 1.3/extensions/external-links/index.html | 498 +++ .../github-flavored-markdown/index.html | 414 +++ 1.3/extensions/inlines-only/index.html | 397 +++ 1.3/extensions/overview/index.html | 490 +++ 1.3/extensions/smart-punctuation/index.html | 417 +++ 1.3/extensions/strikethrough/index.html | 399 +++ 1.3/extensions/tables/index.html | 444 +++ 1.3/extensions/task-lists/index.html | 408 +++ 1.3/index.html | 403 +++ 1.3/installation/index.html | 380 +++ 1.3/security/index.html | 449 +++ 1.3/upgrading/index.html | 387 +++ 1.4/basic-usage/index.html | 408 +++ 1.4/changelog/index.html | 1056 ++++++ 1.4/command-line/index.html | 418 +++ 1.4/configuration/index.html | 438 +++ .../abstract-syntax-tree/index.html | 433 +++ 1.4/customization/block-parsing/index.html | 485 +++ 1.4/customization/block-rendering/index.html | 471 +++ 1.4/customization/cursor/index.html | 507 +++ .../delimiter-processing/index.html | 461 +++ .../document-processing/index.html | 51 + 1.4/customization/environment/index.html | 468 +++ 1.4/customization/event-dispatcher/index.html | 519 +++ 1.4/customization/extensions/index.html | 408 +++ 1.4/customization/inline-parsing/index.html | 531 +++ 1.4/customization/inline-rendering/index.html | 471 +++ 1.4/customization/overview/index.html | 456 +++ 1.4/extensions/autolinks/index.html | 456 +++ 1.4/extensions/commonmark/index.html | 436 +++ 1.4/extensions/disallowed-raw-html/index.html | 425 +++ 1.4/extensions/external-links/index.html | 513 +++ .../github-flavored-markdown/index.html | 422 +++ 1.4/extensions/heading-permalinks/index.html | 545 ++++ 1.4/extensions/inlines-only/index.html | 405 +++ 1.4/extensions/overview/index.html | 510 +++ 1.4/extensions/smart-punctuation/index.html | 425 +++ 1.4/extensions/strikethrough/index.html | 407 +++ 1.4/extensions/table-of-contents/index.html | 557 ++++ 1.4/extensions/tables/index.html | 452 +++ 1.4/extensions/task-lists/index.html | 416 +++ 1.4/index.html | 411 +++ 1.4/installation/index.html | 388 +++ 1.4/security/index.html | 461 +++ 1.4/upgrading/index.html | 477 +++ 1.5/basic-usage/index.html | 420 +++ 1.5/changelog/index.html | 1068 ++++++ 1.5/command-line/index.html | 430 +++ 1.5/configuration/index.html | 450 +++ .../abstract-syntax-tree/index.html | 445 +++ 1.5/customization/block-parsing/index.html | 497 +++ 1.5/customization/block-rendering/index.html | 483 +++ 1.5/customization/cursor/index.html | 519 +++ .../delimiter-processing/index.html | 473 +++ .../document-processing/index.html | 51 + 1.5/customization/environment/index.html | 480 +++ 1.5/customization/event-dispatcher/index.html | 531 +++ 1.5/customization/extensions/index.html | 419 +++ 1.5/customization/inline-parsing/index.html | 543 +++ 1.5/customization/inline-rendering/index.html | 483 +++ 1.5/customization/overview/index.html | 468 +++ 1.5/extensions/attributes/index.html | 442 +++ 1.5/extensions/autolinks/index.html | 424 +++ 1.5/extensions/commonmark/index.html | 448 +++ 1.5/extensions/disallowed-raw-html/index.html | 437 +++ 1.5/extensions/external-links/index.html | 544 +++ 1.5/extensions/footnotes/index.html | 493 +++ .../github-flavored-markdown/index.html | 434 +++ 1.5/extensions/heading-permalinks/index.html | 620 ++++ 1.5/extensions/inlines-only/index.html | 417 +++ 1.5/extensions/mentions/index.html | 633 ++++ 1.5/extensions/overview/index.html | 540 +++ 1.5/extensions/smart-punctuation/index.html | 437 +++ 1.5/extensions/strikethrough/index.html | 419 +++ 1.5/extensions/table-of-contents/index.html | 575 ++++ 1.5/extensions/tables/index.html | 464 +++ 1.5/extensions/task-lists/index.html | 428 +++ 1.5/index.html | 423 +++ 1.5/installation/index.html | 400 +++ 1.5/security/index.html | 473 +++ 1.5/upgrading/index.html | 476 +++ 1.6/basic-usage/index.html | 446 +++ 1.6/changelog/index.html | 1072 ++++++ 1.6/command-line/index.html | 434 +++ 1.6/configuration/index.html | 487 +++ .../abstract-syntax-tree/index.html | 449 +++ 1.6/customization/block-parsing/index.html | 501 +++ 1.6/customization/block-rendering/index.html | 487 +++ 1.6/customization/cursor/index.html | 523 +++ .../delimiter-processing/index.html | 477 +++ 1.6/customization/environment/index.html | 481 +++ 1.6/customization/event-dispatcher/index.html | 535 +++ 1.6/customization/extensions/index.html | 424 +++ 1.6/customization/inline-parsing/index.html | 547 ++++ 1.6/customization/inline-rendering/index.html | 487 +++ 1.6/customization/overview/index.html | 472 +++ 1.6/extensions/attributes/index.html | 446 +++ 1.6/extensions/autolinks/index.html | 433 +++ 1.6/extensions/commonmark/index.html | 452 +++ 1.6/extensions/disallowed-raw-html/index.html | 446 +++ 1.6/extensions/external-links/index.html | 548 ++++ 1.6/extensions/footnotes/index.html | 497 +++ .../github-flavored-markdown/index.html | 440 +++ 1.6/extensions/heading-permalinks/index.html | 624 ++++ 1.6/extensions/inlines-only/index.html | 424 +++ 1.6/extensions/mentions/index.html | 637 ++++ 1.6/extensions/overview/index.html | 546 ++++ 1.6/extensions/smart-punctuation/index.html | 441 +++ 1.6/extensions/strikethrough/index.html | 423 +++ 1.6/extensions/table-of-contents/index.html | 579 ++++ 1.6/extensions/tables/index.html | 468 +++ 1.6/extensions/task-lists/index.html | 432 +++ 1.6/index.html | 427 +++ 1.6/installation/index.html | 404 +++ 1.6/security/index.html | 477 +++ 1.6/support/index.html | 409 +++ 1.6/upgrading/index.html | 522 +++ 2.0/basic-usage/index.html | 496 +++ 2.0/changelog/index.html | 1299 ++++++++ 2.0/configuration/index.html | 507 +++ .../abstract-syntax-tree/index.html | 702 ++++ 2.0/customization/block-parsing/index.html | 546 ++++ 2.0/customization/configuration/index.html | 502 +++ 2.0/customization/cursor/index.html | 539 +++ .../delimiter-processing/index.html | 498 +++ 2.0/customization/environment/index.html | 500 +++ 2.0/customization/event-dispatcher/index.html | 574 ++++ 2.0/customization/extensions/index.html | 441 +++ 2.0/customization/inline-parsing/index.html | 592 ++++ 2.0/customization/overview/index.html | 479 +++ 2.0/customization/rendering/index.html | 541 +++ 2.0/customization/slug-normalizer/index.html | 501 +++ 2.0/extensions/attributes/index.html | 482 +++ 2.0/extensions/autolinks/index.html | 449 +++ 2.0/extensions/commonmark/index.html | 450 +++ 2.0/extensions/default-attributes/index.html | 519 +++ 2.0/extensions/description-lists/index.html | 469 +++ 2.0/extensions/disallowed-raw-html/index.html | 476 +++ 2.0/extensions/external-links/index.html | 556 ++++ 2.0/extensions/footnotes/index.html | 556 ++++ 2.0/extensions/front-matter/index.html | 542 +++ .../github-flavored-markdown/index.html | 467 +++ 2.0/extensions/heading-permalinks/index.html | 608 ++++ 2.0/extensions/inlines-only/index.html | 440 +++ 2.0/extensions/mentions/index.html | 666 ++++ 2.0/extensions/overview/index.html | 592 ++++ 2.0/extensions/smart-punctuation/index.html | 459 +++ 2.0/extensions/strikethrough/index.html | 444 +++ 2.0/extensions/table-of-contents/index.html | 597 ++++ 2.0/extensions/tables/index.html | 489 +++ 2.0/extensions/task-lists/index.html | 453 +++ 2.0/index.html | 443 +++ 2.0/installation/index.html | 418 +++ 2.0/security/index.html | 493 +++ 2.0/support/index.html | 425 +++ 2.0/upgrading/consumers/index.html | 706 ++++ 2.0/upgrading/developers/index.html | 1348 ++++++++ 2.0/upgrading/index.html | 421 +++ 2.0/upgrading/integrators/index.html | 1117 +++++++ 2.0/xml/index.html | 448 +++ 2.1/basic-usage/index.html | 496 +++ 2.1/changelog/index.html | 829 +++++ 2.1/configuration/index.html | 507 +++ .../abstract-syntax-tree/index.html | 702 ++++ 2.1/customization/block-parsing/index.html | 546 ++++ 2.1/customization/configuration/index.html | 502 +++ 2.1/customization/cursor/index.html | 539 +++ .../delimiter-processing/index.html | 498 +++ 2.1/customization/environment/index.html | 500 +++ 2.1/customization/event-dispatcher/index.html | 574 ++++ 2.1/customization/extensions/index.html | 441 +++ 2.1/customization/inline-parsing/index.html | 592 ++++ 2.1/customization/overview/index.html | 479 +++ 2.1/customization/rendering/index.html | 541 +++ 2.1/customization/slug-normalizer/index.html | 501 +++ 2.1/extensions/attributes/index.html | 482 +++ 2.1/extensions/autolinks/index.html | 449 +++ 2.1/extensions/commonmark/index.html | 450 +++ 2.1/extensions/default-attributes/index.html | 519 +++ 2.1/extensions/description-lists/index.html | 469 +++ 2.1/extensions/disallowed-raw-html/index.html | 476 +++ 2.1/extensions/external-links/index.html | 556 ++++ 2.1/extensions/footnotes/index.html | 556 ++++ 2.1/extensions/front-matter/index.html | 542 +++ .../github-flavored-markdown/index.html | 467 +++ 2.1/extensions/heading-permalinks/index.html | 615 ++++ 2.1/extensions/inlines-only/index.html | 440 +++ 2.1/extensions/mentions/index.html | 666 ++++ 2.1/extensions/overview/index.html | 592 ++++ 2.1/extensions/smart-punctuation/index.html | 459 +++ 2.1/extensions/strikethrough/index.html | 444 +++ 2.1/extensions/table-of-contents/index.html | 597 ++++ 2.1/extensions/tables/index.html | 489 +++ 2.1/extensions/task-lists/index.html | 453 +++ 2.1/index.html | 443 +++ 2.1/installation/index.html | 418 +++ 2.1/security/index.html | 493 +++ 2.1/support/index.html | 425 +++ 2.1/upgrading/index.html | 409 +++ 2.1/xml/index.html | 448 +++ 2.2/basic-usage/index.html | 496 +++ 2.2/changelog/index.html | 788 +++++ 2.2/configuration/index.html | 507 +++ .../abstract-syntax-tree/index.html | 702 ++++ 2.2/customization/block-parsing/index.html | 546 ++++ 2.2/customization/configuration/index.html | 502 +++ 2.2/customization/cursor/index.html | 539 +++ .../delimiter-processing/index.html | 498 +++ 2.2/customization/environment/index.html | 500 +++ 2.2/customization/event-dispatcher/index.html | 574 ++++ 2.2/customization/extensions/index.html | 441 +++ 2.2/customization/inline-parsing/index.html | 592 ++++ 2.2/customization/overview/index.html | 479 +++ 2.2/customization/rendering/index.html | 560 ++++ 2.2/customization/slug-normalizer/index.html | 501 +++ 2.2/extensions/attributes/index.html | 482 +++ 2.2/extensions/autolinks/index.html | 449 +++ 2.2/extensions/commonmark/index.html | 450 +++ 2.2/extensions/default-attributes/index.html | 519 +++ 2.2/extensions/description-lists/index.html | 469 +++ 2.2/extensions/disallowed-raw-html/index.html | 476 +++ 2.2/extensions/external-links/index.html | 556 ++++ 2.2/extensions/footnotes/index.html | 556 ++++ 2.2/extensions/front-matter/index.html | 542 +++ .../github-flavored-markdown/index.html | 467 +++ 2.2/extensions/heading-permalinks/index.html | 615 ++++ 2.2/extensions/inlines-only/index.html | 440 +++ 2.2/extensions/mentions/index.html | 666 ++++ 2.2/extensions/overview/index.html | 592 ++++ 2.2/extensions/smart-punctuation/index.html | 459 +++ 2.2/extensions/strikethrough/index.html | 444 +++ 2.2/extensions/table-of-contents/index.html | 597 ++++ 2.2/extensions/tables/index.html | 522 +++ 2.2/extensions/task-lists/index.html | 453 +++ 2.2/index.html | 443 +++ 2.2/installation/index.html | 418 +++ 2.2/security/index.html | 493 +++ 2.2/support/index.html | 425 +++ 2.2/upgrading/index.html | 412 +++ 2.2/xml/index.html | 446 +++ 2.3/basic-usage/index.html | 500 +++ 2.3/changelog/index.html | 722 ++++ 2.3/configuration/index.html | 511 +++ .../abstract-syntax-tree/index.html | 706 ++++ 2.3/customization/block-parsing/index.html | 550 ++++ 2.3/customization/configuration/index.html | 506 +++ 2.3/customization/cursor/index.html | 543 +++ .../delimiter-processing/index.html | 502 +++ 2.3/customization/environment/index.html | 504 +++ 2.3/customization/event-dispatcher/index.html | 578 ++++ 2.3/customization/extensions/index.html | 445 +++ 2.3/customization/inline-parsing/index.html | 596 ++++ 2.3/customization/overview/index.html | 483 +++ 2.3/customization/rendering/index.html | 560 ++++ 2.3/customization/slug-normalizer/index.html | 505 +++ 2.3/extensions/attributes/index.html | 486 +++ 2.3/extensions/autolinks/index.html | 453 +++ 2.3/extensions/commonmark/index.html | 454 +++ 2.3/extensions/default-attributes/index.html | 523 +++ 2.3/extensions/description-lists/index.html | 473 +++ 2.3/extensions/disallowed-raw-html/index.html | 480 +++ 2.3/extensions/embed/index.html | 552 ++++ 2.3/extensions/external-links/index.html | 560 ++++ 2.3/extensions/footnotes/index.html | 560 ++++ 2.3/extensions/front-matter/index.html | 546 ++++ .../github-flavored-markdown/index.html | 471 +++ 2.3/extensions/heading-permalinks/index.html | 619 ++++ 2.3/extensions/inlines-only/index.html | 444 +++ 2.3/extensions/mentions/index.html | 670 ++++ 2.3/extensions/overview/index.html | 602 ++++ 2.3/extensions/smart-punctuation/index.html | 463 +++ 2.3/extensions/strikethrough/index.html | 448 +++ 2.3/extensions/table-of-contents/index.html | 601 ++++ 2.3/extensions/tables/index.html | 526 +++ 2.3/extensions/task-lists/index.html | 457 +++ 2.3/index.html | 447 +++ 2.3/installation/index.html | 422 +++ 2.3/security/index.html | 497 +++ 2.3/support/index.html | 429 +++ 2.3/upgrading/index.html | 415 +++ 2.3/xml/index.html | 450 +++ 2.4/basic-usage/index.html | 504 +++ 2.4/changelog/index.html | 627 ++++ 2.4/configuration/index.html | 515 +++ .../abstract-syntax-tree/index.html | 710 ++++ 2.4/customization/block-parsing/index.html | 554 ++++ 2.4/customization/configuration/index.html | 510 +++ 2.4/customization/cursor/index.html | 547 ++++ .../delimiter-processing/index.html | 506 +++ .../disabling-features/index.html | 475 +++ 2.4/customization/environment/index.html | 508 +++ 2.4/customization/event-dispatcher/index.html | 582 ++++ 2.4/customization/extensions/index.html | 449 +++ 2.4/customization/inline-parsing/index.html | 600 ++++ 2.4/customization/overview/index.html | 487 +++ 2.4/customization/rendering/index.html | 564 ++++ 2.4/customization/slug-normalizer/index.html | 509 +++ 2.4/extensions/attributes/index.html | 490 +++ 2.4/extensions/autolinks/index.html | 457 +++ 2.4/extensions/commonmark/index.html | 458 +++ 2.4/extensions/default-attributes/index.html | 527 +++ 2.4/extensions/description-lists/index.html | 477 +++ 2.4/extensions/disallowed-raw-html/index.html | 484 +++ 2.4/extensions/embed/index.html | 556 ++++ 2.4/extensions/external-links/index.html | 562 ++++ 2.4/extensions/footnotes/index.html | 564 ++++ 2.4/extensions/front-matter/index.html | 550 ++++ .../github-flavored-markdown/index.html | 475 +++ 2.4/extensions/heading-permalinks/index.html | 633 ++++ 2.4/extensions/inlines-only/index.html | 448 +++ 2.4/extensions/mentions/index.html | 674 ++++ 2.4/extensions/overview/index.html | 606 ++++ 2.4/extensions/smart-punctuation/index.html | 467 +++ 2.4/extensions/strikethrough/index.html | 452 +++ 2.4/extensions/table-of-contents/index.html | 605 ++++ 2.4/extensions/tables/index.html | 565 ++++ 2.4/extensions/task-lists/index.html | 461 +++ 2.4/index.html | 451 +++ 2.4/installation/index.html | 426 +++ 2.4/security/index.html | 501 +++ 2.4/support/index.html | 433 +++ 2.4/upgrading/index.html | 434 +++ 2.4/xml/index.html | 454 +++ 2.5/basic-usage/index.html | 502 +++ 2.5/changelog/index.html | 515 +++ 2.5/configuration/index.html | 513 +++ .../abstract-syntax-tree/index.html | 708 ++++ 2.5/customization/block-parsing/index.html | 552 ++++ 2.5/customization/configuration/index.html | 508 +++ 2.5/customization/cursor/index.html | 545 ++++ .../delimiter-processing/index.html | 504 +++ .../disabling-features/index.html | 473 +++ 2.5/customization/environment/index.html | 506 +++ 2.5/customization/event-dispatcher/index.html | 580 ++++ 2.5/customization/extensions/index.html | 447 +++ 2.5/customization/inline-parsing/index.html | 598 ++++ 2.5/customization/overview/index.html | 485 +++ 2.5/customization/rendering/index.html | 562 ++++ 2.5/customization/slug-normalizer/index.html | 507 +++ 2.5/extensions/attributes/index.html | 501 +++ 2.5/extensions/autolinks/index.html | 472 +++ 2.5/extensions/commonmark/index.html | 456 +++ 2.5/extensions/default-attributes/index.html | 525 +++ 2.5/extensions/description-lists/index.html | 475 +++ 2.5/extensions/disallowed-raw-html/index.html | 482 +++ 2.5/extensions/embed/index.html | 563 ++++ 2.5/extensions/external-links/index.html | 560 ++++ 2.5/extensions/footnotes/index.html | 562 ++++ 2.5/extensions/front-matter/index.html | 548 ++++ .../github-flavored-markdown/index.html | 473 +++ 2.5/extensions/heading-permalinks/index.html | 631 ++++ 2.5/extensions/inlines-only/index.html | 446 +++ 2.5/extensions/mentions/index.html | 672 ++++ 2.5/extensions/overview/index.html | 604 ++++ 2.5/extensions/smart-punctuation/index.html | 465 +++ 2.5/extensions/strikethrough/index.html | 450 +++ 2.5/extensions/table-of-contents/index.html | 603 ++++ 2.5/extensions/tables/index.html | 563 ++++ 2.5/extensions/task-lists/index.html | 459 +++ 2.5/index.html | 449 +++ 2.5/installation/index.html | 424 +++ 2.5/security/index.html | 499 +++ 2.5/support/index.html | 431 +++ 2.5/upgrading/index.html | 415 +++ 2.5/xml/index.html | 452 +++ CNAME | 1 + README.md | 7 + basic-usage/index.html | 51 + changelog/index.html | 51 + command-line/index.html | 51 + configuration/index.html | 51 + custom.css | 430 +++ custom.js | 18 + customization/abstract-syntax-tree/index.html | 51 + customization/block-parsing/index.html | 51 + customization/block-rendering/index.html | 51 + customization/configuration/index.html | 51 + customization/cursor/index.html | 51 + customization/delimiter-processing/index.html | 51 + customization/disabling-features/index.html | 51 + customization/document-processing/index.html | 51 + customization/environment/index.html | 51 + customization/event-dispatcher/index.html | 51 + customization/extensions/index.html | 51 + customization/index.html | 51 + customization/inline-parsing/index.html | 51 + customization/inline-rendering/index.html | 51 + customization/overview/index.html | 51 + customization/slug-normalizer/index.html | 51 + extensions/attributes/index.html | 51 + extensions/autolinks/index.html | 51 + extensions/commonmark/index.html | 51 + extensions/default-attributes/index.html | 51 + extensions/description-lists/index.html | 51 + extensions/disallowed-raw-html/index.html | 51 + extensions/embed/index.html | 51 + extensions/external-links/index.html | 51 + extensions/footnotes/index.html | 51 + extensions/front-matter/index.html | 51 + .../github-flavored-markdown/index.html | 51 + extensions/heading-permalinks/index.html | 51 + extensions/index.html | 51 + extensions/inlines-only/index.html | 51 + extensions/mentions/index.html | 51 + extensions/overview/index.html | 51 + extensions/smart-punctuation/index.html | 51 + extensions/strikethrough/index.html | 51 + extensions/table-of-contents/index.html | 51 + extensions/tables/index.html | 51 + extensions/task-lists/index.html | 51 + global.css | 42 + homepage.css | 381 +++ images/commonmark-banner.png | Bin 0 -> 124118 bytes images/commonmark-social.png | Bin 0 -> 310396 bytes images/users/cachet.svg | 16 + images/users/daux.png | Bin 0 -> 4282 bytes images/users/drupal.svg | 1 + images/users/laravel.svg | 68 + images/users/neos.svg | 1 + images/users/twig.svg | 1 + index.html | 218 ++ installation/index.html | 51 + redirects.json | 1 + releases/index.html | 2902 +++++++++++++++++ security/index.html | 51 + support.css | 112 + support/index.html | 51 + upgrading/changelog/index.html | 51 + upgrading/index.html | 51 + xml/index.html | 51 + 490 files changed, 209791 insertions(+) create mode 100644 .nojekyll create mode 100644 0.20/basic-usage/index.html create mode 100644 0.20/changelog/index.html create mode 100644 0.20/command-line/index.html create mode 100644 0.20/configuration/index.html create mode 100644 0.20/customization/abstract-syntax-tree/index.html create mode 100644 0.20/customization/block-parsing/index.html create mode 100644 0.20/customization/block-rendering/index.html create mode 100644 0.20/customization/cursor/index.html create mode 100644 0.20/customization/delimiter-processing/index.html create mode 100644 0.20/customization/document-processing/index.html create mode 100644 0.20/customization/environment/index.html create mode 100644 0.20/customization/extensions/index.html create mode 100644 0.20/customization/index.html create mode 100644 0.20/customization/inline-parsing/index.html create mode 100644 0.20/customization/inline-rendering/index.html create mode 100644 0.20/customization/overview/index.html create mode 100644 0.20/index.html create mode 100644 0.20/installation/index.html create mode 100644 0.20/security/index.html create mode 100644 0.20/upgrading/index.html create mode 100644 1.0/basic-usage/index.html create mode 100644 1.0/changelog/index.html create mode 100644 1.0/command-line/index.html create mode 100644 1.0/configuration/index.html create mode 100644 1.0/customization/abstract-syntax-tree/index.html create mode 100644 1.0/customization/block-parsing/index.html create mode 100644 1.0/customization/block-rendering/index.html create mode 100644 1.0/customization/cursor/index.html create mode 100644 1.0/customization/delimiter-processing/index.html create mode 100644 1.0/customization/document-processing/index.html create mode 100644 1.0/customization/environment/index.html create mode 100644 1.0/customization/event-dispatcher/index.html create mode 100644 1.0/customization/extensions/index.html create mode 100644 1.0/customization/inline-parsing/index.html create mode 100644 1.0/customization/inline-rendering/index.html create mode 100644 1.0/customization/overview/index.html create mode 100644 1.0/index.html create mode 100644 1.0/installation/index.html create mode 100644 1.0/security/index.html create mode 100644 1.0/upgrading/index.html create mode 100644 1.3/basic-usage/index.html create mode 100644 1.3/changelog/index.html create mode 100644 1.3/command-line/index.html create mode 100644 1.3/configuration/index.html create mode 100644 1.3/customization/abstract-syntax-tree/index.html create mode 100644 1.3/customization/block-parsing/index.html create mode 100644 1.3/customization/block-rendering/index.html create mode 100644 1.3/customization/cursor/index.html create mode 100644 1.3/customization/delimiter-processing/index.html create mode 100644 1.3/customization/document-processing/index.html create mode 100644 1.3/customization/environment/index.html create mode 100644 1.3/customization/event-dispatcher/index.html create mode 100644 1.3/customization/extensions/index.html create mode 100644 1.3/customization/inline-parsing/index.html create mode 100644 1.3/customization/inline-rendering/index.html create mode 100644 1.3/customization/overview/index.html create mode 100644 1.3/extensions/autolinks/index.html create mode 100644 1.3/extensions/commonmark/index.html create mode 100644 1.3/extensions/disallowed-raw-html/index.html create mode 100644 1.3/extensions/external-links/index.html create mode 100644 1.3/extensions/github-flavored-markdown/index.html create mode 100644 1.3/extensions/inlines-only/index.html create mode 100644 1.3/extensions/overview/index.html create mode 100644 1.3/extensions/smart-punctuation/index.html create mode 100644 1.3/extensions/strikethrough/index.html create mode 100644 1.3/extensions/tables/index.html create mode 100644 1.3/extensions/task-lists/index.html create mode 100644 1.3/index.html create mode 100644 1.3/installation/index.html create mode 100644 1.3/security/index.html create mode 100644 1.3/upgrading/index.html create mode 100644 1.4/basic-usage/index.html create mode 100644 1.4/changelog/index.html create mode 100644 1.4/command-line/index.html create mode 100644 1.4/configuration/index.html create mode 100644 1.4/customization/abstract-syntax-tree/index.html create mode 100644 1.4/customization/block-parsing/index.html create mode 100644 1.4/customization/block-rendering/index.html create mode 100644 1.4/customization/cursor/index.html create mode 100644 1.4/customization/delimiter-processing/index.html create mode 100644 1.4/customization/document-processing/index.html create mode 100644 1.4/customization/environment/index.html create mode 100644 1.4/customization/event-dispatcher/index.html create mode 100644 1.4/customization/extensions/index.html create mode 100644 1.4/customization/inline-parsing/index.html create mode 100644 1.4/customization/inline-rendering/index.html create mode 100644 1.4/customization/overview/index.html create mode 100644 1.4/extensions/autolinks/index.html create mode 100644 1.4/extensions/commonmark/index.html create mode 100644 1.4/extensions/disallowed-raw-html/index.html create mode 100644 1.4/extensions/external-links/index.html create mode 100644 1.4/extensions/github-flavored-markdown/index.html create mode 100644 1.4/extensions/heading-permalinks/index.html create mode 100644 1.4/extensions/inlines-only/index.html create mode 100644 1.4/extensions/overview/index.html create mode 100644 1.4/extensions/smart-punctuation/index.html create mode 100644 1.4/extensions/strikethrough/index.html create mode 100644 1.4/extensions/table-of-contents/index.html create mode 100644 1.4/extensions/tables/index.html create mode 100644 1.4/extensions/task-lists/index.html create mode 100644 1.4/index.html create mode 100644 1.4/installation/index.html create mode 100644 1.4/security/index.html create mode 100644 1.4/upgrading/index.html create mode 100644 1.5/basic-usage/index.html create mode 100644 1.5/changelog/index.html create mode 100644 1.5/command-line/index.html create mode 100644 1.5/configuration/index.html create mode 100644 1.5/customization/abstract-syntax-tree/index.html create mode 100644 1.5/customization/block-parsing/index.html create mode 100644 1.5/customization/block-rendering/index.html create mode 100644 1.5/customization/cursor/index.html create mode 100644 1.5/customization/delimiter-processing/index.html create mode 100644 1.5/customization/document-processing/index.html create mode 100644 1.5/customization/environment/index.html create mode 100644 1.5/customization/event-dispatcher/index.html create mode 100644 1.5/customization/extensions/index.html create mode 100644 1.5/customization/inline-parsing/index.html create mode 100644 1.5/customization/inline-rendering/index.html create mode 100644 1.5/customization/overview/index.html create mode 100644 1.5/extensions/attributes/index.html create mode 100644 1.5/extensions/autolinks/index.html create mode 100644 1.5/extensions/commonmark/index.html create mode 100644 1.5/extensions/disallowed-raw-html/index.html create mode 100644 1.5/extensions/external-links/index.html create mode 100644 1.5/extensions/footnotes/index.html create mode 100644 1.5/extensions/github-flavored-markdown/index.html create mode 100644 1.5/extensions/heading-permalinks/index.html create mode 100644 1.5/extensions/inlines-only/index.html create mode 100644 1.5/extensions/mentions/index.html create mode 100644 1.5/extensions/overview/index.html create mode 100644 1.5/extensions/smart-punctuation/index.html create mode 100644 1.5/extensions/strikethrough/index.html create mode 100644 1.5/extensions/table-of-contents/index.html create mode 100644 1.5/extensions/tables/index.html create mode 100644 1.5/extensions/task-lists/index.html create mode 100644 1.5/index.html create mode 100644 1.5/installation/index.html create mode 100644 1.5/security/index.html create mode 100644 1.5/upgrading/index.html create mode 100644 1.6/basic-usage/index.html create mode 100644 1.6/changelog/index.html create mode 100644 1.6/command-line/index.html create mode 100644 1.6/configuration/index.html create mode 100644 1.6/customization/abstract-syntax-tree/index.html create mode 100644 1.6/customization/block-parsing/index.html create mode 100644 1.6/customization/block-rendering/index.html create mode 100644 1.6/customization/cursor/index.html create mode 100644 1.6/customization/delimiter-processing/index.html create mode 100644 1.6/customization/environment/index.html create mode 100644 1.6/customization/event-dispatcher/index.html create mode 100644 1.6/customization/extensions/index.html create mode 100644 1.6/customization/inline-parsing/index.html create mode 100644 1.6/customization/inline-rendering/index.html create mode 100644 1.6/customization/overview/index.html create mode 100644 1.6/extensions/attributes/index.html create mode 100644 1.6/extensions/autolinks/index.html create mode 100644 1.6/extensions/commonmark/index.html create mode 100644 1.6/extensions/disallowed-raw-html/index.html create mode 100644 1.6/extensions/external-links/index.html create mode 100644 1.6/extensions/footnotes/index.html create mode 100644 1.6/extensions/github-flavored-markdown/index.html create mode 100644 1.6/extensions/heading-permalinks/index.html create mode 100644 1.6/extensions/inlines-only/index.html create mode 100644 1.6/extensions/mentions/index.html create mode 100644 1.6/extensions/overview/index.html create mode 100644 1.6/extensions/smart-punctuation/index.html create mode 100644 1.6/extensions/strikethrough/index.html create mode 100644 1.6/extensions/table-of-contents/index.html create mode 100644 1.6/extensions/tables/index.html create mode 100644 1.6/extensions/task-lists/index.html create mode 100644 1.6/index.html create mode 100644 1.6/installation/index.html create mode 100644 1.6/security/index.html create mode 100644 1.6/support/index.html create mode 100644 1.6/upgrading/index.html create mode 100644 2.0/basic-usage/index.html create mode 100644 2.0/changelog/index.html create mode 100644 2.0/configuration/index.html create mode 100644 2.0/customization/abstract-syntax-tree/index.html create mode 100644 2.0/customization/block-parsing/index.html create mode 100644 2.0/customization/configuration/index.html create mode 100644 2.0/customization/cursor/index.html create mode 100644 2.0/customization/delimiter-processing/index.html create mode 100644 2.0/customization/environment/index.html create mode 100644 2.0/customization/event-dispatcher/index.html create mode 100644 2.0/customization/extensions/index.html create mode 100644 2.0/customization/inline-parsing/index.html create mode 100644 2.0/customization/overview/index.html create mode 100644 2.0/customization/rendering/index.html create mode 100644 2.0/customization/slug-normalizer/index.html create mode 100644 2.0/extensions/attributes/index.html create mode 100644 2.0/extensions/autolinks/index.html create mode 100644 2.0/extensions/commonmark/index.html create mode 100644 2.0/extensions/default-attributes/index.html create mode 100644 2.0/extensions/description-lists/index.html create mode 100644 2.0/extensions/disallowed-raw-html/index.html create mode 100644 2.0/extensions/external-links/index.html create mode 100644 2.0/extensions/footnotes/index.html create mode 100644 2.0/extensions/front-matter/index.html create mode 100644 2.0/extensions/github-flavored-markdown/index.html create mode 100644 2.0/extensions/heading-permalinks/index.html create mode 100644 2.0/extensions/inlines-only/index.html create mode 100644 2.0/extensions/mentions/index.html create mode 100644 2.0/extensions/overview/index.html create mode 100644 2.0/extensions/smart-punctuation/index.html create mode 100644 2.0/extensions/strikethrough/index.html create mode 100644 2.0/extensions/table-of-contents/index.html create mode 100644 2.0/extensions/tables/index.html create mode 100644 2.0/extensions/task-lists/index.html create mode 100644 2.0/index.html create mode 100644 2.0/installation/index.html create mode 100644 2.0/security/index.html create mode 100644 2.0/support/index.html create mode 100644 2.0/upgrading/consumers/index.html create mode 100644 2.0/upgrading/developers/index.html create mode 100644 2.0/upgrading/index.html create mode 100644 2.0/upgrading/integrators/index.html create mode 100644 2.0/xml/index.html create mode 100644 2.1/basic-usage/index.html create mode 100644 2.1/changelog/index.html create mode 100644 2.1/configuration/index.html create mode 100644 2.1/customization/abstract-syntax-tree/index.html create mode 100644 2.1/customization/block-parsing/index.html create mode 100644 2.1/customization/configuration/index.html create mode 100644 2.1/customization/cursor/index.html create mode 100644 2.1/customization/delimiter-processing/index.html create mode 100644 2.1/customization/environment/index.html create mode 100644 2.1/customization/event-dispatcher/index.html create mode 100644 2.1/customization/extensions/index.html create mode 100644 2.1/customization/inline-parsing/index.html create mode 100644 2.1/customization/overview/index.html create mode 100644 2.1/customization/rendering/index.html create mode 100644 2.1/customization/slug-normalizer/index.html create mode 100644 2.1/extensions/attributes/index.html create mode 100644 2.1/extensions/autolinks/index.html create mode 100644 2.1/extensions/commonmark/index.html create mode 100644 2.1/extensions/default-attributes/index.html create mode 100644 2.1/extensions/description-lists/index.html create mode 100644 2.1/extensions/disallowed-raw-html/index.html create mode 100644 2.1/extensions/external-links/index.html create mode 100644 2.1/extensions/footnotes/index.html create mode 100644 2.1/extensions/front-matter/index.html create mode 100644 2.1/extensions/github-flavored-markdown/index.html create mode 100644 2.1/extensions/heading-permalinks/index.html create mode 100644 2.1/extensions/inlines-only/index.html create mode 100644 2.1/extensions/mentions/index.html create mode 100644 2.1/extensions/overview/index.html create mode 100644 2.1/extensions/smart-punctuation/index.html create mode 100644 2.1/extensions/strikethrough/index.html create mode 100644 2.1/extensions/table-of-contents/index.html create mode 100644 2.1/extensions/tables/index.html create mode 100644 2.1/extensions/task-lists/index.html create mode 100644 2.1/index.html create mode 100644 2.1/installation/index.html create mode 100644 2.1/security/index.html create mode 100644 2.1/support/index.html create mode 100644 2.1/upgrading/index.html create mode 100644 2.1/xml/index.html create mode 100644 2.2/basic-usage/index.html create mode 100644 2.2/changelog/index.html create mode 100644 2.2/configuration/index.html create mode 100644 2.2/customization/abstract-syntax-tree/index.html create mode 100644 2.2/customization/block-parsing/index.html create mode 100644 2.2/customization/configuration/index.html create mode 100644 2.2/customization/cursor/index.html create mode 100644 2.2/customization/delimiter-processing/index.html create mode 100644 2.2/customization/environment/index.html create mode 100644 2.2/customization/event-dispatcher/index.html create mode 100644 2.2/customization/extensions/index.html create mode 100644 2.2/customization/inline-parsing/index.html create mode 100644 2.2/customization/overview/index.html create mode 100644 2.2/customization/rendering/index.html create mode 100644 2.2/customization/slug-normalizer/index.html create mode 100644 2.2/extensions/attributes/index.html create mode 100644 2.2/extensions/autolinks/index.html create mode 100644 2.2/extensions/commonmark/index.html create mode 100644 2.2/extensions/default-attributes/index.html create mode 100644 2.2/extensions/description-lists/index.html create mode 100644 2.2/extensions/disallowed-raw-html/index.html create mode 100644 2.2/extensions/external-links/index.html create mode 100644 2.2/extensions/footnotes/index.html create mode 100644 2.2/extensions/front-matter/index.html create mode 100644 2.2/extensions/github-flavored-markdown/index.html create mode 100644 2.2/extensions/heading-permalinks/index.html create mode 100644 2.2/extensions/inlines-only/index.html create mode 100644 2.2/extensions/mentions/index.html create mode 100644 2.2/extensions/overview/index.html create mode 100644 2.2/extensions/smart-punctuation/index.html create mode 100644 2.2/extensions/strikethrough/index.html create mode 100644 2.2/extensions/table-of-contents/index.html create mode 100644 2.2/extensions/tables/index.html create mode 100644 2.2/extensions/task-lists/index.html create mode 100644 2.2/index.html create mode 100644 2.2/installation/index.html create mode 100644 2.2/security/index.html create mode 100644 2.2/support/index.html create mode 100644 2.2/upgrading/index.html create mode 100644 2.2/xml/index.html create mode 100644 2.3/basic-usage/index.html create mode 100644 2.3/changelog/index.html create mode 100644 2.3/configuration/index.html create mode 100644 2.3/customization/abstract-syntax-tree/index.html create mode 100644 2.3/customization/block-parsing/index.html create mode 100644 2.3/customization/configuration/index.html create mode 100644 2.3/customization/cursor/index.html create mode 100644 2.3/customization/delimiter-processing/index.html create mode 100644 2.3/customization/environment/index.html create mode 100644 2.3/customization/event-dispatcher/index.html create mode 100644 2.3/customization/extensions/index.html create mode 100644 2.3/customization/inline-parsing/index.html create mode 100644 2.3/customization/overview/index.html create mode 100644 2.3/customization/rendering/index.html create mode 100644 2.3/customization/slug-normalizer/index.html create mode 100644 2.3/extensions/attributes/index.html create mode 100644 2.3/extensions/autolinks/index.html create mode 100644 2.3/extensions/commonmark/index.html create mode 100644 2.3/extensions/default-attributes/index.html create mode 100644 2.3/extensions/description-lists/index.html create mode 100644 2.3/extensions/disallowed-raw-html/index.html create mode 100644 2.3/extensions/embed/index.html create mode 100644 2.3/extensions/external-links/index.html create mode 100644 2.3/extensions/footnotes/index.html create mode 100644 2.3/extensions/front-matter/index.html create mode 100644 2.3/extensions/github-flavored-markdown/index.html create mode 100644 2.3/extensions/heading-permalinks/index.html create mode 100644 2.3/extensions/inlines-only/index.html create mode 100644 2.3/extensions/mentions/index.html create mode 100644 2.3/extensions/overview/index.html create mode 100644 2.3/extensions/smart-punctuation/index.html create mode 100644 2.3/extensions/strikethrough/index.html create mode 100644 2.3/extensions/table-of-contents/index.html create mode 100644 2.3/extensions/tables/index.html create mode 100644 2.3/extensions/task-lists/index.html create mode 100644 2.3/index.html create mode 100644 2.3/installation/index.html create mode 100644 2.3/security/index.html create mode 100644 2.3/support/index.html create mode 100644 2.3/upgrading/index.html create mode 100644 2.3/xml/index.html create mode 100644 2.4/basic-usage/index.html create mode 100644 2.4/changelog/index.html create mode 100644 2.4/configuration/index.html create mode 100644 2.4/customization/abstract-syntax-tree/index.html create mode 100644 2.4/customization/block-parsing/index.html create mode 100644 2.4/customization/configuration/index.html create mode 100644 2.4/customization/cursor/index.html create mode 100644 2.4/customization/delimiter-processing/index.html create mode 100644 2.4/customization/disabling-features/index.html create mode 100644 2.4/customization/environment/index.html create mode 100644 2.4/customization/event-dispatcher/index.html create mode 100644 2.4/customization/extensions/index.html create mode 100644 2.4/customization/inline-parsing/index.html create mode 100644 2.4/customization/overview/index.html create mode 100644 2.4/customization/rendering/index.html create mode 100644 2.4/customization/slug-normalizer/index.html create mode 100644 2.4/extensions/attributes/index.html create mode 100644 2.4/extensions/autolinks/index.html create mode 100644 2.4/extensions/commonmark/index.html create mode 100644 2.4/extensions/default-attributes/index.html create mode 100644 2.4/extensions/description-lists/index.html create mode 100644 2.4/extensions/disallowed-raw-html/index.html create mode 100644 2.4/extensions/embed/index.html create mode 100644 2.4/extensions/external-links/index.html create mode 100644 2.4/extensions/footnotes/index.html create mode 100644 2.4/extensions/front-matter/index.html create mode 100644 2.4/extensions/github-flavored-markdown/index.html create mode 100644 2.4/extensions/heading-permalinks/index.html create mode 100644 2.4/extensions/inlines-only/index.html create mode 100644 2.4/extensions/mentions/index.html create mode 100644 2.4/extensions/overview/index.html create mode 100644 2.4/extensions/smart-punctuation/index.html create mode 100644 2.4/extensions/strikethrough/index.html create mode 100644 2.4/extensions/table-of-contents/index.html create mode 100644 2.4/extensions/tables/index.html create mode 100644 2.4/extensions/task-lists/index.html create mode 100644 2.4/index.html create mode 100644 2.4/installation/index.html create mode 100644 2.4/security/index.html create mode 100644 2.4/support/index.html create mode 100644 2.4/upgrading/index.html create mode 100644 2.4/xml/index.html create mode 100644 2.5/basic-usage/index.html create mode 100644 2.5/changelog/index.html create mode 100644 2.5/configuration/index.html create mode 100644 2.5/customization/abstract-syntax-tree/index.html create mode 100644 2.5/customization/block-parsing/index.html create mode 100644 2.5/customization/configuration/index.html create mode 100644 2.5/customization/cursor/index.html create mode 100644 2.5/customization/delimiter-processing/index.html create mode 100644 2.5/customization/disabling-features/index.html create mode 100644 2.5/customization/environment/index.html create mode 100644 2.5/customization/event-dispatcher/index.html create mode 100644 2.5/customization/extensions/index.html create mode 100644 2.5/customization/inline-parsing/index.html create mode 100644 2.5/customization/overview/index.html create mode 100644 2.5/customization/rendering/index.html create mode 100644 2.5/customization/slug-normalizer/index.html create mode 100644 2.5/extensions/attributes/index.html create mode 100644 2.5/extensions/autolinks/index.html create mode 100644 2.5/extensions/commonmark/index.html create mode 100644 2.5/extensions/default-attributes/index.html create mode 100644 2.5/extensions/description-lists/index.html create mode 100644 2.5/extensions/disallowed-raw-html/index.html create mode 100644 2.5/extensions/embed/index.html create mode 100644 2.5/extensions/external-links/index.html create mode 100644 2.5/extensions/footnotes/index.html create mode 100644 2.5/extensions/front-matter/index.html create mode 100644 2.5/extensions/github-flavored-markdown/index.html create mode 100644 2.5/extensions/heading-permalinks/index.html create mode 100644 2.5/extensions/inlines-only/index.html create mode 100644 2.5/extensions/mentions/index.html create mode 100644 2.5/extensions/overview/index.html create mode 100644 2.5/extensions/smart-punctuation/index.html create mode 100644 2.5/extensions/strikethrough/index.html create mode 100644 2.5/extensions/table-of-contents/index.html create mode 100644 2.5/extensions/tables/index.html create mode 100644 2.5/extensions/task-lists/index.html create mode 100644 2.5/index.html create mode 100644 2.5/installation/index.html create mode 100644 2.5/security/index.html create mode 100644 2.5/support/index.html create mode 100644 2.5/upgrading/index.html create mode 100644 2.5/xml/index.html create mode 100644 CNAME create mode 100644 README.md create mode 100644 basic-usage/index.html create mode 100644 changelog/index.html create mode 100644 command-line/index.html create mode 100644 configuration/index.html create mode 100644 custom.css create mode 100644 custom.js create mode 100644 customization/abstract-syntax-tree/index.html create mode 100644 customization/block-parsing/index.html create mode 100644 customization/block-rendering/index.html create mode 100644 customization/configuration/index.html create mode 100644 customization/cursor/index.html create mode 100644 customization/delimiter-processing/index.html create mode 100644 customization/disabling-features/index.html create mode 100644 customization/document-processing/index.html create mode 100644 customization/environment/index.html create mode 100644 customization/event-dispatcher/index.html create mode 100644 customization/extensions/index.html create mode 100644 customization/index.html create mode 100644 customization/inline-parsing/index.html create mode 100644 customization/inline-rendering/index.html create mode 100644 customization/overview/index.html create mode 100644 customization/slug-normalizer/index.html create mode 100644 extensions/attributes/index.html create mode 100644 extensions/autolinks/index.html create mode 100644 extensions/commonmark/index.html create mode 100644 extensions/default-attributes/index.html create mode 100644 extensions/description-lists/index.html create mode 100644 extensions/disallowed-raw-html/index.html create mode 100644 extensions/embed/index.html create mode 100644 extensions/external-links/index.html create mode 100644 extensions/footnotes/index.html create mode 100644 extensions/front-matter/index.html create mode 100644 extensions/github-flavored-markdown/index.html create mode 100644 extensions/heading-permalinks/index.html create mode 100644 extensions/index.html create mode 100644 extensions/inlines-only/index.html create mode 100644 extensions/mentions/index.html create mode 100644 extensions/overview/index.html create mode 100644 extensions/smart-punctuation/index.html create mode 100644 extensions/strikethrough/index.html create mode 100644 extensions/table-of-contents/index.html create mode 100644 extensions/tables/index.html create mode 100644 extensions/task-lists/index.html create mode 100644 global.css create mode 100644 homepage.css create mode 100644 images/commonmark-banner.png create mode 100644 images/commonmark-social.png create mode 100644 images/users/cachet.svg create mode 100644 images/users/daux.png create mode 100755 images/users/drupal.svg create mode 100644 images/users/laravel.svg create mode 100644 images/users/neos.svg create mode 100644 images/users/twig.svg create mode 100644 index.html create mode 100644 installation/index.html create mode 100644 redirects.json create mode 100644 releases/index.html create mode 100644 security/index.html create mode 100644 support.css create mode 100644 support/index.html create mode 100644 upgrading/changelog/index.html create mode 100644 upgrading/index.html create mode 100644 xml/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/0.20/basic-usage/index.html b/0.20/basic-usage/index.html new file mode 100644 index 0000000000..1b0eac0643 --- /dev/null +++ b/0.20/basic-usage/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/changelog/index.html b/0.20/changelog/index.html new file mode 100644 index 0000000000..08fe0717b9 --- /dev/null +++ b/0.20/changelog/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/command-line/index.html b/0.20/command-line/index.html new file mode 100644 index 0000000000..63fb4aa98c --- /dev/null +++ b/0.20/command-line/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/configuration/index.html b/0.20/configuration/index.html new file mode 100644 index 0000000000..70628e37c3 --- /dev/null +++ b/0.20/configuration/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/abstract-syntax-tree/index.html b/0.20/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..8afa76d6b2 --- /dev/null +++ b/0.20/customization/abstract-syntax-tree/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/block-parsing/index.html b/0.20/customization/block-parsing/index.html new file mode 100644 index 0000000000..f016e0f92a --- /dev/null +++ b/0.20/customization/block-parsing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/block-rendering/index.html b/0.20/customization/block-rendering/index.html new file mode 100644 index 0000000000..f9755a4fb4 --- /dev/null +++ b/0.20/customization/block-rendering/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/cursor/index.html b/0.20/customization/cursor/index.html new file mode 100644 index 0000000000..2713501fbf --- /dev/null +++ b/0.20/customization/cursor/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/delimiter-processing/index.html b/0.20/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..e80043d812 --- /dev/null +++ b/0.20/customization/delimiter-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/document-processing/index.html b/0.20/customization/document-processing/index.html new file mode 100644 index 0000000000..334f85ffb5 --- /dev/null +++ b/0.20/customization/document-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/environment/index.html b/0.20/customization/environment/index.html new file mode 100644 index 0000000000..2868ae7b3e --- /dev/null +++ b/0.20/customization/environment/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/extensions/index.html b/0.20/customization/extensions/index.html new file mode 100644 index 0000000000..eebdb10549 --- /dev/null +++ b/0.20/customization/extensions/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/index.html b/0.20/customization/index.html new file mode 100644 index 0000000000..92d0f965cb --- /dev/null +++ b/0.20/customization/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/inline-parsing/index.html b/0.20/customization/inline-parsing/index.html new file mode 100644 index 0000000000..9e584ae5d4 --- /dev/null +++ b/0.20/customization/inline-parsing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/inline-rendering/index.html b/0.20/customization/inline-rendering/index.html new file mode 100644 index 0000000000..2875af878e --- /dev/null +++ b/0.20/customization/inline-rendering/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/customization/overview/index.html b/0.20/customization/overview/index.html new file mode 100644 index 0000000000..92d0f965cb --- /dev/null +++ b/0.20/customization/overview/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/index.html b/0.20/index.html new file mode 100644 index 0000000000..b8380bb675 --- /dev/null +++ b/0.20/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/installation/index.html b/0.20/installation/index.html new file mode 100644 index 0000000000..b89d4a47a0 --- /dev/null +++ b/0.20/installation/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/security/index.html b/0.20/security/index.html new file mode 100644 index 0000000000..0d10b187cc --- /dev/null +++ b/0.20/security/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/0.20/upgrading/index.html b/0.20/upgrading/index.html new file mode 100644 index 0000000000..2419209b40 --- /dev/null +++ b/0.20/upgrading/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/1.0/basic-usage/index.html b/1.0/basic-usage/index.html new file mode 100644 index 0000000000..a66a98a2af --- /dev/null +++ b/1.0/basic-usage/index.html @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

The CommonMarkConverter class provides a simple wrapper for converting CommonMark to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The actual conversion process has three steps:

+ +
    +
  1. Creating an Environment, adding whichever extensions/parser/renders you need
  2. +
  3. Parsing the Markdown input into an AST
  4. +
  5. Rendering the AST document as HTML
  6. +
+ +

CommonMarkConverter handles this for you, but you can execute that process yourself if you wish:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+// <h1>Hello World!</h1>
+
+ +

Additional customization is also possible.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/changelog/index.html b/1.0/changelog/index.html new file mode 100644 index 0000000000..898454d3bc --- /dev/null +++ b/1.0/changelog/index.html @@ -0,0 +1,623 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 1.0 - 1.2 releases are shown below. See the full list of releases for the complete changelog.

+ +

1.2.2 - 2020-01-16

+ +

This release contains the same changes as 1.1.3:

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.1.3 - 2020-01-16

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.2.1 - 2020-01-15

+ +

Changed

+ +
    +
  • Introduced several micro-optimizations, reducing the parse time by 8%
  • +
+ +

1.2.0 - 2020-01-09

+ +

Changed

+ +
    +
  • Removed URL decoding step before encoding (more performant and better matches the JS library)
  • +
  • Removed redundant token from HTML tag regex
  • +
+ +

1.1.2 - 2019-12-10

+ +

Fixed

+ +
    +
  • Fixed URL normalization not handling non-UTF-8 sequences properly (#395, #396)
  • +
+ +

1.1.1 - 2019-11-11

+ +

Fixed

+ +
    +
  • Fixed handling of link destinations with unbalanced unescaped parens
  • +
  • Fixed adding delimiters to stack which can neither open nor close a run
  • +
+ +

1.1.0 - 2019-10-31

+ +

Added

+ +
    +
  • Added a new Html5EntityDecoder class (#387)
  • +
+ +

Changed

+ +
    +
  • Improved performance by 10% (#389)
  • +
  • Made entity decoding less memory-intensive (#386, #387)
  • +
+ +

Fixed

+ +
    +
  • Fixed PHP 7.4 compatibility issues
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the Html5Entities class - use Html5EntityDecoder instead (#387)
  • +
+ +

1.0.0 - 2019-06-29

+ +

First stable release! 🎉

+ +

No code changes have been introduced since 1.0.0-rc1

+ +

1.0.0-rc1 - 2019-06-20

+ +

Added

+ +
    +
  • Extracted a ReferenceMapInterface from the ReferenceMap class
  • +
  • Added optional ReferenceMapInterface parameter to the Document constructor
  • +
+ +

Changed

+ +
    +
  • Replaced all references to ReferenceMap with ReferenceMapInterface
  • +
  • ReferenceMap::addReference() no longer returns $this
  • +
+ +

Fixed

+ +
    +
  • Fixed bug where elements with content of "0" wouldn’t be rendered (#376)
  • +
+ +

1.0.0-beta4 - 2019-06-05

+ +

Added

+ +
    +
  • Added event dispatcher functionality (#359, #372)
  • +
+ +

Removed

+ +
    +
  • Removed DocumentProcessorInterface functionality in favor of event dispatching (#373)
  • +
+ +

1.0.0-beta3 - 2019-05-28

+ +

Changed

+ +
    +
  • Made the Delimiter class final and extracted a new DelimiterInterface +
      +
    • Modified most external usages to use this new interface
    • +
    +
  • +
  • Renamed three Delimiter methods: +
      +
    • getOrigDelims() renamed to getOriginalLength()
    • +
    • getNumDelims() renamed to getLength()
    • +
    • setNumDelims() renamed to setLength()
    • +
    +
  • +
  • Made additional classes final: +
      +
    • DelimiterStack
    • +
    • ReferenceMap
    • +
    • ReferenceParser
    • +
    +
  • +
  • Moved ReferenceParser into the Reference sub-namespace
  • +
+ +

Removed

+ +
    +
  • Removed unused Delimiter methods: +
      +
    • setCanOpen()
    • +
    • setCanClose()
    • +
    • setChar()
    • +
    • setIndex()
    • +
    • setInlineNode()
    • +
    +
  • +
  • Removed fluent interface from Delimiter (setter methods now have no return values)
  • +
+ +

1.0.0-beta2 - 2019-05-27

+ +

This beta release fixes a couple of items that were not addressed in the previous beta.

+ +

Changed

+ +
    +
  • DelimiterProcessorInterface::process() will accept any type of AbstractStringContainer now, not just Text nodes
  • +
  • The Delimiter constructor, getInlineNode(), and setInlineNode() no longer accept generic Node elements - only AbstractStringContainers
  • +
+ +

Removed

+ +
    +
  • Removed all deprecated functionality: +
      +
    • The safe option (use html_input and allow_unsafe_links options instead)
    • +
    • All deprecated RegexHelper constants
    • +
    • DocParser::getEnvironment() (you should obtain it some other way)
    • +
    • AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
    • +
    +
  • +
+ +

1.0.0-beta1 - 2019-05-26

+ +

See the upgrading guide for additional information.

+ +

Added

+ +
    +
  • Added proper support for delimiters, including custom delimiters +
      +
    • addDelimiterProcessor() added to ConfigurableEnvironmentInterface and Environment
    • +
    +
  • +
  • Basic delimiters no longer need custom parsers - they’ll be parsed automatically
  • +
  • Added new methods: +
      +
    • AdjacentTextMerger::mergeTextNodesBetweenExclusive()
    • +
    • CommonMarkConveter::getEnvironment()
    • +
    • Configuration::set()
    • +
    +
  • +
  • Extracted some new interfaces from base classes: +
      +
    • DocParserInterface created from DocParser
    • +
    • ConfigurationInterface created from Configuration
    • +
    • ReferenceInterface created from Reference
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Renamed several methods of the Configuration class: +
      +
    • getConfig() renamed to get()
    • +
    • mergeConfig() renamed to merge()
    • +
    • setConfig() renamed to replace()
    • +
    +
  • +
  • Changed ConfigurationAwareInterface::setConfiguration() to accept the new ConfigurationInterface instead of the concrete class
  • +
  • Renamed the AdjoiningTextCollapser class to AdjacentTextMerger +
      +
    • Replaced its collapseTextNodes() method with the new mergeChildNodes() method
    • +
    +
  • +
  • Made several classes final: +
      +
    • Configuration
    • +
    • DocParser
    • +
    • HtmlRenderer
    • +
    • InlineParserEngine
    • +
    • NodeWalker
    • +
    • Reference
    • +
    • All of the block/inline parsers and renderers
    • +
    +
  • +
  • Reduced visibility of several internal methods to private: +
      +
    • DelimiterStack::findEarliest()
    • +
    • All protected methods in InlineParserEngine
    • +
    +
  • +
  • Marked some classes and methods as @internal
  • +
  • ElementRendererInterface now requires a public renderInline() method; added this to HtmlRenderer
  • +
  • Changed InlineParserEngine::parse() to require an AbstractStringContainerBlock instead of the generic Node class
  • +
  • Un-deprecated the CommonmarkConverter::VERSION constant
  • +
  • The Converter constructor now requires an instance of DocParserInterface instead of the concrete DocParser
  • +
  • Changed Emphasis, Strong, and AbstractWebResource to directly extend AbstractInline instead of the (now-deprecated) intermediary AbstractInlineContainer class
  • +
+ +

Fixed

+ +
    +
  • Fixed null errors when inserting sibling Nodes without parents
  • +
  • Fixed NodeWalkerEvent not requiring a Node via its constructor
  • +
  • Fixed Reference::normalizeReference() improperly converting to uppercase instead of performing proper Unicode case-folding
  • +
  • Fixed strong emphasis delimiters not being preserved when enable_strong is set to false (it now works identically to enable_em)
  • +
+ +

Deprecated

+ +
    +
  • Deprecated DocParser::getEnvironment() (you should obtain it some other way)
  • +
  • Deprecated AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
  • +
+ +

Removed

+ +
    +
  • Removed inline processor functionality now that we have proper delimiter support: +
      +
    • Removed addInlineProcessor() from ConfigurableEnvironmentInterface and Environment
    • +
    • Removed getInlineProcessors() from EnvironmentInterface and Environment
    • +
    • Removed EmphasisProcessor
    • +
    • Removed InlineProcessorInterface
    • +
    +
  • +
  • Removed EmphasisParser now that we have proper delimiter support
  • +
  • Removed support for non-UTF-8-compatible encodings +
      +
    • Removed getEncoding() from ContextInterface
    • +
    • Removed getEncoding(), setEncoding(), and $encoding from Context
    • +
    • Removed getEncoding() and the second $encoding constructor param from Cursor
    • +
    +
  • +
  • Removed now-unused methods +
      +
    • Removed DelimiterStack::getTop() (no replacement)
    • +
    • Removed DelimiterStack::iterateByCharacters() (use the new processDelimiters() method instead)
    • +
    • Removed the protected DelimiterStack::findMatchingOpener() method
    • +
    +
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/command-line/index.html b/1.0/command-line/index.html new file mode 100644 index 0000000000..e2edd43c60 --- /dev/null +++ b/1.0/command-line/index.html @@ -0,0 +1,357 @@ + + + + + + + + + + + + + + + + + Command Line - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Command Line

+ +

Markdown can be converted at the command line using the ./bin/commonmark script.

+ +

Usage

+ +
./bin/commonmark [OPTIONS] [FILE]
+
+ +
    +
  • -h, --help: Shows help and usage information
  • +
  • --enable-em: Disable <em> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --enable-strong: Disable <strong> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --use-asterisk: Disable parsing of * for emphasis by setting to 0; enable with 1 (default: 1)
  • +
  • --use-underscore: Disable parsing of _ for emphasis by setting to 0; enable with 1 (default: 1)
  • +
+ +

If no file is given, input will be read from STDIN.

+ +

Output will be written to STDOUT.

+ +

Examples

+ +

Converting a file named document.md

+ +
./bin/commonmark document.md
+
+ +

Converting a file and saving its output

+ +
./bin/commonmark document.md > output.html
+
+ +

Converting from STDIN

+ +
echo -e '# Hello World!' | ./bin/commonmark
+
+ +

Converting from STDIN and saving the output

+ +
echo -e '# Hello World!' | ./bin/commonmark > output.html
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/configuration/index.html b/1.0/configuration/index.html new file mode 100644 index 0000000000..21e1198a45 --- /dev/null +++ b/1.0/configuration/index.html @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

You can provide an array of configuration options to the CommonMarkConverter when creating it:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter([
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'enable_em' => true,
+    'enable_strong' => true,
+    'use_asterisk' => true,
+    'use_underscore' => true,
+    'unordered_list_markers' => ['-', '*', '+'],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => INF,
+]);
+
+ +

Here’s a list of currently-supported options:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
  • +
  • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
  • +
  • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
  • +
  • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
  • +
  • unordered_list_markers - Array of characters that can be used to indicated a bulleted list (default: ["-", "*", "+"])
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: infinite). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested. Added in 0.17.
  • +
+ +

Additional configuration options are available for some of the available extensions - refer to their individual documentation for more details.

+ +

The following options have been deprecated. They will no longer work once 1.0.0 is released:

+ +
    +
  • safe - Prevents rendering of raw HTML if set to true (default: false)
  • +
+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

The Environment also exposes three methods for managing the configuration:

+ +
    +
  • setConfig(array $config = []) - Replace the current configuration with something else
  • +
  • mergeConfig(array $config = []) - Recursively merge the current configuration with the given options
  • +
  • getConfig(string $key, $default = null) - Returns the config value. For nested configs, use a /-separate path; for example: renderer/soft_break
  • +
+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/abstract-syntax-tree/index.html b/1.0/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..8c2bd99429 --- /dev/null +++ b/1.0/customization/abstract-syntax-tree/index.html @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Traversal

+ +

The following methods can be used to traverse the AST:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

Iteration / Walking the Tree

+ +

If you’d like to iterate through all the nodes, use the walker() method to obtain an instance of NodeWalker. This will walk through the entire tree, emitting NodeWalkerEvents along the way.

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'I am ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

This walker doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes.

+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/block-parsing/index.html b/1.0/customization/block-parsing/index.html new file mode 100644 index 0000000000..f1062de274 --- /dev/null +++ b/1.0/customization/block-parsing/index.html @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

Block parsers should implement BlockParserInterface and implement the following method:

+ +

parse()

+ +
public function parse(ContextInterface $context, Cursor $cursor): bool;
+
+ +

When parsing a new line, the DocParser iterates through all registered block parsers and calls their parse() method. Each parser must determine whether it can handle the given line; if so, it should parse the given block and return true.

+ +

Parameters

+ +
    +
  • ContextInterface $context - Provides information about the current context of the DocParser. Includes access to things like the document, current block container, and more.
  • +
  • Cursor $cursor - The Cursor encapsulates the current state of the line being parsed and provides helpers for looking around the current position.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the line will be parsed as text.

+ +

Returning true tells the engine that you’ve successfully parsed the block at the given position. It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of syntax indicating the block start
  2. +
  3. Add the parsed block via $context->addBlock()
  4. +
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible
  • +
  • Your parse() method may be called thousands of times so be sure your code is optimized
  • +
+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

Block elements also play a role during the parsing process as they tell the underlying engine how to handle subsequent blocks that are found.

+ +

AbstractBlockElement Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
canContain(...)Tell the engine whether a subsequent block can be added as a child of yours
isCode()Returns whether this block represents an extra-greedy <code> block
matchesNextLine(...)Returns whether this block continues onto the next line (some blocks are multi-line)
shouldLastLineBeBlank()Returns whether the last line should be blank (primarily used by ListItem elements)
finalize(...)Finalizes the block after all child items have been added, thus marking it as closed for modification
+ +

For examples on how these methods are used, see the core block element classes included with this library.

+ +

AbstractStringContainerBlock

+ +

If your element can contain strings of text, you should extend AbstractStringContainerBlock instead of AbstractBlock. This provides some additional methods needed to manage that inner text:

+ + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
handleRemainingContents(...)This is called when a block has been created but some other text still exists on that line
addLine(...)Adds the given line of text to the block element
getStringContent()Returns the strings contained with that block element
+ +

InlineContainerInterface

+ +

If the text contained by your block should be parsed for inline elements, you should also implement the InlineContainerInterface. This doesn’t add any new methods but does signal to the engine that inline parsing is required.

+ +

Multi-line Code Blocks

+ +

If you have a block which spans multiple lines and doesn’t contain any child blocks, consider having isCode() return true. Code blocks have a special feature which enables “greedy parsing” - once it first parses your block, the engine will assume that most of the subsequent lines of Markdown belong to your block - it won’t try using any other parsers until your parser’s matchesNextLine() method returns false, indicating that we’ve reached the end of that code block.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/block-rendering/index.html b/1.0/customization/block-rendering/index.html new file mode 100644 index 0000000000..36803cc1d7 --- /dev/null +++ b/1.0/customization/block-rendering/index.html @@ -0,0 +1,412 @@ + + + + + + + + + + + + + + + + + Block Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Block Rendering

+ +

Block renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement BlockRendererInterface and its render() method:

+ +

render()

+ +
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false);
+
+ +

The HtmlRenderer will call this method whenever a supported block element is encountered in the AST being rendered.

+ +

If the method can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractBlock $block - The encountered block you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to render inlines or easily generate HTML tags
  • +
  • $inTightList = false - Whether the element is being rendered in a tight list or not
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the block and any of its contents. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Block Renderers

+ +

When registering your renderer, you must tell the Environment which block element class your renderer should handle. For example:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the block class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addBlockRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple block types:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Block\Element\IndentedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addBlockRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addBlockRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Node\Block\AbstractBlock;
+use League\CommonMark\Renderer\Block\BlockRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements BlockRendererInterface
+{
+    public function render(AbstractBlock $block, NodeRendererInterface $htmlRenderer, bool $inTightList = false)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addBlockRenderer('League\CommonMark\Block\Element\ThematicBreak', new TextDividerRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any inlines your block might contain!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/cursor/index.html b/1.0/customization/cursor/index.html new file mode 100644 index 0000000000..f5a5c25479 --- /dev/null +++ b/1.0/customization/cursor/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter()Returns the character at the current position
getCharacter(int $index)Returns the character at the given absolute position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/delimiter-processing/index.html b/1.0/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..84ef963b45 --- /dev/null +++ b/1.0/customization/delimiter-processing/index.html @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse);
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/document-processing/index.html b/1.0/customization/document-processing/index.html new file mode 100644 index 0000000000..334f85ffb5 --- /dev/null +++ b/1.0/customization/document-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/1.0/customization/environment/index.html b/1.0/customization/environment/index.html new file mode 100644 index 0000000000..77f0cd80f8 --- /dev/null +++ b/1.0/customization/environment/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

A pre-configured Environment can be obtained like this:

+ +
use League\CommonMark;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+ +

All of the core renders, parsers, etc. needed to implement the CommonMark spec will be pre-registered and ready to go.

+ +

You can customize this default Environment (or even a new, empty one) using any of the methods below (from the ConfigurableEnvironmentInterface interface).

+ +

mergeConfig()

+ +
public function mergeConfig(array $config = []);
+
+ +

Merges the given configuration settings into any existing ones.

+ +

setConfig()

+ +
public function setConfig(array $config = []);
+
+ +

Completely replaces the previous configuration settings with the new $config you provide.

+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. This is typically how you’d integrate third-party extensions with this library.

+ +

addBlockParser()

+ +
public function addBlockParser(BlockParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addBlockRenderer()

+ +
public function addBlockRenderer(string $blockClass, BlockRendererInterface $blockRenderer, int $priority = 0);
+
+ +

Registers a BlockRendererInterface to handle a specific type of block ($blockClass) with the given priority (a higher number will be executed earlier).

+ +

See Block Rendering for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addInlineRenderer()

+ +
public function addInlineRenderer(string $inlineClass, InlineRendererInterface $renderer, int $priority = 0);
+
+ +

Registers an InlineRendererInterface to handle a specific type of inline ($inlineClass) with the given priority (a higher number will be executed earlier). +A single renderer can handle multiple inline classes, but you must register it separately for each type. (The same renderer instance can be re-used if desired.)

+ +

See Inline Rendering for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/event-dispatcher/index.html b/1.0/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..ca7865df99 --- /dev/null +++ b/1.0/customization/event-dispatcher/index.html @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code. If you’re familiar with Symfony’s EventDispatcher or PSR-14 then this should be very familiar to you.

+ +

Event Class

+ +

All events must extend from the AbstractEvent class:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - an instance of AbstractEvent to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Inline\Element\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event)
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data['attributes']['class'] = 'external-link';
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfig('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = Environment::createCommonMarkEnvironment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convertToHtml($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/extensions/index.html b/1.0/customization/extensions/index.html new file mode 100644 index 0000000000..bf73f88cbf --- /dev/null +++ b/1.0/customization/extensions/index.html @@ -0,0 +1,460 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a ConfigurableEnvironmentInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\ConfigurableEnvironmentInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(ConfigurableEnvironmentInterface $environment)
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new EmojiExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello! :wave:');
+
+ +

Included Extensions

+ +

Starting in v1.3, this library includes several extensions to support GitHub-Flavored Markdown. You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+
+ +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

GFM Extensions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeDocumentation
GithubFlavoredMarkdownExtensionEnables full support for GFM. Includes the following sub-extensions by default: 
AutolinkExtensionEnables automatic linking of URLs within text without needing to wrap them with Markdown syntaxDocumentation
DisallowedRawHtmlExtensionDisables certain kinds of HTML tags that could affect page rendering 
StrikethroughExtensionAllows using tilde characters (~~) for ~strikethrough~ formattingDocumentation
TableExtensionEnables you to create HTML tablesDocumentation
TaskListExtensionAllows the creation of task listsDocumentation
+ +

Other Useful Extensions

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeDocumentation
ExternalLinkExtensionTags external links with additional markupDocumentation
InlinesOnlyExtensionOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.Documentation
SmartPunctExtensionIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalentsDocumentation
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/inline-parsing/index.html b/1.0/customization/inline-parsing/index.html new file mode 100644 index 0000000000..cb0e27013d --- /dev/null +++ b/1.0/customization/inline-parsing/index.html @@ -0,0 +1,472 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getCharacters()

+ +

This method should return an array of single characters which the inline parser engine should stop on. When it does find a match in the current line the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has stopped at a matching character; and,
  2. +
  3. No other inline parsers have successfully parsed the character
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser, including the Cursor used to parse the current line.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line/character for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the character will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return ['@'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+        // Save the cursor state in case we need to rewind and bail
+        $previousState = $cursor->saveState();
+        // Advance past the @ symbol to keep parsing simpler
+        $cursor->advance();
+        // Parse the handle
+        $handle = $cursor->match('/^[A-Za-z0-9_]{1,15}(?!\w)/');
+        if (empty($handle)) {
+            // Regex failed to match; this isn't a valid Twitter handle
+            $cursor->restoreState($previousState);
+            return false;
+        }
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Image;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return [':'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // The next character must be a paren; if not, then bail
+        // We use peek() to quickly check without affecting the cursor
+        $nextChar = $cursor->peek();
+        if ($nextChar !== '(' && $nextChar !== ')') {
+            return false;
+        }
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($nextChar === ')') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($nextChar === '(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible.
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/inline-rendering/index.html b/1.0/customization/inline-rendering/index.html new file mode 100644 index 0000000000..5f77bdb347 --- /dev/null +++ b/1.0/customization/inline-rendering/index.html @@ -0,0 +1,412 @@ + + + + + + + + + + + + + + + + + Inline Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Inline Rendering

+ +

Inline renderers are responsible for converting the parsed inline elements into their HTML representation.

+ +

All inline renderers should implement InlineRendererInterface and its render() method:

+ +

render()

+ +

Block elements are responsible for calling $htmlRenderer->renderInlines() if they contain inline elements. This in turns causes the HtmlRenderer to call this render() method whenever a supported inline element is encountered.

+ +

If the method can only handle certain inline types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractInline $inline - The encountered inline you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to help escape output or easily generate HTML tags
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the entire inline and any contents. This can be an HtmlElement object (preferred; castable to a string) or a string of raw HTML.

+ +

You are responsible for handling any escaping that may be necessary.

+ +

Return null if your renderer cannot handle the given inline element - the next-highest priority renderer will then be given a chance to render it.

+ +

Designating Inline Renderers

+ +

When registering your render, you must tell the Environment which inline element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the inline class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addInlineRenderer('League\CommonMark\Inline\Element\Link', new MyCustomLinkRenderer());
+
+ +

Example

+ +

Here’s a custom renderer which puts a special class on links to external sites:

+ +
use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+use League\CommonMark\HtmlElement;
+
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+    private $host;
+
+    public function __construct($host)
+    {
+        $this->host = $host;
+    }
+
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline));
+        }
+
+        $attrs = array();
+
+        $attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true);
+
+        if (isset($inline->attributes['title'])) {
+            $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true);
+        }
+
+        if ($this->isExternalUrl($inline->getUrl())) {
+            $attrs['class'] = 'external-link';
+        }
+
+        return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children()));
+    }
+
+    private function isExternalUrl($url)
+    {
+        return parse_url($url, PHP_URL_HOST) !== $this->host;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineRenderer(Link::class, new MyCustomLinkRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Some inlines can contain other inlines - don’t forget to render those too!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/customization/overview/index.html b/1.0/customization/overview/index.html new file mode 100644 index 0000000000..152d7832ba --- /dev/null +++ b/1.0/customization/overview/index.html @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

This library uses a three-step process to convert Markdown to HTML:

+ +
    +
  1. Parse the various block and inline elements into an Abstract Syntax Tree (AST)
  2. +
  3. Allow users to iterate and modify the parsed AST
  4. +
  5. Render the final AST representation to HTML
  6. +
+ +

You can hook into any of these three steps to customize this library to suit your needs.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renders convert the parsed blocks/inlines from the AST representation into HTML. There are two types of renderers:

+ + + +

When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you +to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/index.html b/1.0/index.html new file mode 100644 index 0000000000..db0cbe4e9e --- /dev/null +++ b/1.0/index.html @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/installation/index.html b/1.0/installation/index.html new file mode 100644 index 0000000000..06592eaf27 --- /dev/null +++ b/1.0/installation/index.html @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +

In your project root just run:

+ +
composer require league/commonmark:^1.0
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^1.0. This is equivalent to >=1.0 <2.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/security/index.html b/1.0/security/index.html new file mode 100644 index 0000000000..1f5c37d251 --- /dev/null +++ b/1.0/security/index.html @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convertToHtml($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.0/upgrading/index.html b/1.0/upgrading/index.html new file mode 100644 index 0000000000..eac1a84873 --- /dev/null +++ b/1.0/upgrading/index.html @@ -0,0 +1,443 @@ + + + + + + + + + + + + + + + + + Upgrading from 0.19 to 1.0 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.0. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 0.19 to 1.0

+ +

Previous Deprecations Removed

+ +

All previously-deprecated code has been removed. This includes:

+ +
    +
  • The safe option (use html_input and allow_unsafe_links options instead)
  • +
  • All deprecated RegexHelper constants
  • +
  • DocParser::getEnvironment() (you should obtain it some other way)
  • +
  • AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
  • +
+ +

Document Processors Removed

+ +

We no longer support Document Processors because we now have event dispatching which can fill that same role! Simply remove the interface from your processor and register it as a listener; for example, instead of this:

+ +
class MyDocumentProcessor implements DocumentProcessorInterface
+{
+    public function processDocument(Document $document)
+    {
+        // TODO: Do things with the $document
+    }
+}
+
+// ...
+
+$processor = new MyDocumentProcessor();
+$environment->addDocumentProcessor($processor);
+
+ +

Simply do this:

+ +
class MyDocumentProcessor
+{
+    public function onDocumentParsed(DocumentParsedEvent $event)
+    {
+        $document = $event->getDocument();
+        // TODO: Do things with the $document
+    }
+}
+
+// ...
+
+$processor = new MyDocumentProcessor();
+$environment->addEventListener(DocumentParsedEvent::class, [$processor, 'onDocumentParsed']);
+
+ +

Text Encoding

+ +

This library used to claim it supported ISO-8859-1 encoding but that never truly worked - everything assumed the text was encoded as UTF-8 or ASCII. We’ve therefore dropped support for ISO-8859-1 and any other unexpected encodings. If you were using some other encoding, you’ll now need to convert your Markdown to UTF-8 prior to running it through this library.

+ +

Additionally, all public getEncoding() or setEncoding() methods have been removed, so assume that you’re working with UTF-8.

+ +

Inline Processors

+ +

The “inline processor” functionality has been removed and replaced with a proper “delimiter processor” feature geared specifically towards dealing with delimiters (which is what the previous implementation tried to do - poorly).

+ +

No direct upgrade path exists as this implementation was not widely used, or only used for implementing delimiter processing. If you fall in the latter category, simply leverage the new functionality instead. Otherwise, if you have another good use case for inline processors, please let us know in the issue tracker.

+ +

Delimiters

+ +

Now that we have proper delimiter handling, we’ve finalized the Delimiter class and extracted a DelimiterInterface from it. If you previous extended from Delimiter you’ll need to implement this new interface instead.

+ +

We also deleted these unused Delimiter methods:

+ +
    +
  • setCanOpen()
  • +
  • setCanClose()
  • +
  • setChar()
  • +
  • setIndex()
  • +
  • setInlineNode()
  • +
+ +

And all of the remaining Delimiter::set___() methods no longer return $this.

+ +

DocParser

+ +

The DocParser class is now final as it was never intended to be extended, especially given how so much logic was in private methods. Any custom implementations should implement the new DocParserInterface interface instead.

+ +

Additionally, the getEnvironment() method has been deprecated and excluded from that new interface, as it was only used internally by the DocParser and other better ways exist to obtain an environment where needed.

+ +

Configuration

+ +

The Configuration class is now final and implements a new ConfigurationInterface. If any of your parsers/renders/etc implement ConfigurationAwareInterface you’ll need to update that method to accept the new interface instead of the concrete class.

+ +

We also renamed/added the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Old NameNew Name
getConfig()get()
n/aset()
setConfig()replace()
mergeConfig()merge()
+ +

AbstractInlineContainer

+ +

The AbstractInlineContainer class added an unnecessary level of inheritance and was therefore deprecated. If you previously extended this class, you should now extend from AbstractInline and override isContainer() to return true.

+ +

AdjoiningTextCollapser

+ +

The AdjoiningTextCollapser is an internal class used to combine multiple Text elements into one. If you were using this yourself (unlikely) you’ll need to refer to its new name instead: AdjacentTextMerger. And if you previously used collapseTextNodes() you’ll want to switch to using mergeChildNodes() instead.

+ +

References

+ +

The ReferenceParser was moved into the Reference sub-namespace, so update your imports accordingly.

+ +

Virtually all usages of ReferenceMap in type hints have been replaced with the new ReferenceMapInterface. This interface has the same methods, with one small change: addReference() no longer returns $this.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/basic-usage/index.html b/1.3/basic-usage/index.html new file mode 100644 index 0000000000..a7217f2f02 --- /dev/null +++ b/1.3/basic-usage/index.html @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The actual conversion process has three steps:

+ +
    +
  1. Creating an Environment, adding whichever extensions/parser/renders you need
  2. +
  3. Parsing the Markdown input into an AST
  4. +
  5. Rendering the AST document as HTML
  6. +
+ +

CommonMarkConverter handles this for you, but you can execute that process yourself if you wish:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+// <h1>Hello World!</h1>
+
+ +

Additional customization is also possible, and we have many handy extensions to enable additional syntax and features.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/changelog/index.html b/1.3/changelog/index.html new file mode 100644 index 0000000000..10953bd184 --- /dev/null +++ b/1.3/changelog/index.html @@ -0,0 +1,724 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 1.x releases are shown below. See the full list of releases for the complete changelog.

+ +

1.3.4 - 2020-04-13

+ +

Fixed

+ +
    +
  • Fixed configuration/environment not being injected into event listeners when adding them via [$instance, 'method'] callable syntax (#440)
  • +
+ +

1.3.3 - 2020-04-05

+ +

Fixed

+ +
    +
  • Fixed event listeners not having the environment or configuration injected if they implemented the EnvironmentAwareInterface or ConfigurationAwareInterface (#423)
  • +
+ +

1.3.2 - 2020-03-25

+ +

Fixed

+ +
    +
  • Optimized URL normalization in cases where URLs don’t contain special characters (#417, #418)
  • +
+ +

1.3.1 - 2020-02-28

+ +

Fixed

+ +
    +
  • Fixed return types of Environment::createCommonMarkEnvironment() and Environment::createGFMEnvironment()
  • +
+ +

1.3.0 - 2020-02-09

+ +

ℹ️ Do you use league/commonmark-ext* packages? Those features are now included directly in this library! See #409 for details on making the switch.

+ +

Added

+ +
    +
  • Added (optional) full GFM support! 🎉🎉🎉 (#409)
  • +
  • Added check to ensure Markdown input is valid UTF-8 (#401, #405)
  • +
  • Added new unordered_list_markers configuration option (#408, #411)
  • +
+ +

Changed

+ +
    +
  • Introduced several micro-optimizations for a 5-10% performance boost
  • +
+ +

1.2.2 - 2020-01-16

+ +

This release contains the same changes as 1.1.3:

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.1.3 - 2020-01-16

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.2.1 - 2020-01-15

+ +

Changed

+ +
    +
  • Introduced several micro-optimizations, reducing the parse time by 8%
  • +
+ +

1.2.0 - 2020-01-09

+ +

Changed

+ +
    +
  • Removed URL decoding step before encoding (more performant and better matches the JS library)
  • +
  • Removed redundant token from HTML tag regex
  • +
+ +

1.1.2 - 2019-12-10

+ +

Fixed

+ +
    +
  • Fixed URL normalization not handling non-UTF-8 sequences properly (#395, #396)
  • +
+ +

1.1.1 - 2019-11-11

+ +

Fixed

+ +
    +
  • Fixed handling of link destinations with unbalanced unescaped parens
  • +
  • Fixed adding delimiters to stack which can neither open nor close a run
  • +
+ +

1.1.0 - 2019-10-31

+ +

Added

+ +
    +
  • Added a new Html5EntityDecoder class (#387)
  • +
+ +

Changed

+ +
    +
  • Improved performance by 10% (#389)
  • +
  • Made entity decoding less memory-intensive (#386, #387)
  • +
+ +

Fixed

+ +
    +
  • Fixed PHP 7.4 compatibility issues
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the Html5Entities class - use Html5EntityDecoder instead (#387)
  • +
+ +

1.0.0 - 2019-06-29

+ +

First stable release! 🎉

+ +

No code changes have been introduced since 1.0.0-rc1

+ +

1.0.0-rc1 - 2019-06-20

+ +

Added

+ +
    +
  • Extracted a ReferenceMapInterface from the ReferenceMap class
  • +
  • Added optional ReferenceMapInterface parameter to the Document constructor
  • +
+ +

Changed

+ +
    +
  • Replaced all references to ReferenceMap with ReferenceMapInterface
  • +
  • ReferenceMap::addReference() no longer returns $this
  • +
+ +

Fixed

+ +
    +
  • Fixed bug where elements with content of "0" wouldn’t be rendered (#376)
  • +
+ +

1.0.0-beta4 - 2019-06-05

+ +

Added

+ +
    +
  • Added event dispatcher functionality (#359, #372)
  • +
+ +

Removed

+ +
    +
  • Removed DocumentProcessorInterface functionality in favor of event dispatching (#373)
  • +
+ +

1.0.0-beta3 - 2019-05-28

+ +

Changed

+ +
    +
  • Made the Delimiter class final and extracted a new DelimiterInterface +
      +
    • Modified most external usages to use this new interface
    • +
    +
  • +
  • Renamed three Delimiter methods: +
      +
    • getOrigDelims() renamed to getOriginalLength()
    • +
    • getNumDelims() renamed to getLength()
    • +
    • setNumDelims() renamed to setLength()
    • +
    +
  • +
  • Made additional classes final: +
      +
    • DelimiterStack
    • +
    • ReferenceMap
    • +
    • ReferenceParser
    • +
    +
  • +
  • Moved ReferenceParser into the Reference sub-namespace
  • +
+ +

Removed

+ +
    +
  • Removed unused Delimiter methods: +
      +
    • setCanOpen()
    • +
    • setCanClose()
    • +
    • setChar()
    • +
    • setIndex()
    • +
    • setInlineNode()
    • +
    +
  • +
  • Removed fluent interface from Delimiter (setter methods now have no return values)
  • +
+ +

1.0.0-beta2 - 2019-05-27

+ +

This beta release fixes a couple of items that were not addressed in the previous beta.

+ +

Changed

+ +
    +
  • DelimiterProcessorInterface::process() will accept any type of AbstractStringContainer now, not just Text nodes
  • +
  • The Delimiter constructor, getInlineNode(), and setInlineNode() no longer accept generic Node elements - only AbstractStringContainers
  • +
+ +

Removed

+ +
    +
  • Removed all deprecated functionality: +
      +
    • The safe option (use html_input and allow_unsafe_links options instead)
    • +
    • All deprecated RegexHelper constants
    • +
    • DocParser::getEnvironment() (you should obtain it some other way)
    • +
    • AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
    • +
    +
  • +
+ +

1.0.0-beta1 - 2019-05-26

+ +

See the upgrading guide for additional information.

+ +

Added

+ +
    +
  • Added proper support for delimiters, including custom delimiters +
      +
    • addDelimiterProcessor() added to ConfigurableEnvironmentInterface and Environment
    • +
    +
  • +
  • Basic delimiters no longer need custom parsers - they’ll be parsed automatically
  • +
  • Added new methods: +
      +
    • AdjacentTextMerger::mergeTextNodesBetweenExclusive()
    • +
    • CommonMarkConveter::getEnvironment()
    • +
    • Configuration::set()
    • +
    +
  • +
  • Extracted some new interfaces from base classes: +
      +
    • DocParserInterface created from DocParser
    • +
    • ConfigurationInterface created from Configuration
    • +
    • ReferenceInterface created from Reference
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Renamed several methods of the Configuration class: +
      +
    • getConfig() renamed to get()
    • +
    • mergeConfig() renamed to merge()
    • +
    • setConfig() renamed to replace()
    • +
    +
  • +
  • Changed ConfigurationAwareInterface::setConfiguration() to accept the new ConfigurationInterface instead of the concrete class
  • +
  • Renamed the AdjoiningTextCollapser class to AdjacentTextMerger +
      +
    • Replaced its collapseTextNodes() method with the new mergeChildNodes() method
    • +
    +
  • +
  • Made several classes final: +
      +
    • Configuration
    • +
    • DocParser
    • +
    • HtmlRenderer
    • +
    • InlineParserEngine
    • +
    • NodeWalker
    • +
    • Reference
    • +
    • All of the block/inline parsers and renderers
    • +
    +
  • +
  • Reduced visibility of several internal methods to private: +
      +
    • DelimiterStack::findEarliest()
    • +
    • All protected methods in InlineParserEngine
    • +
    +
  • +
  • Marked some classes and methods as @internal
  • +
  • ElementRendererInterface now requires a public renderInline() method; added this to HtmlRenderer
  • +
  • Changed InlineParserEngine::parse() to require an AbstractStringContainerBlock instead of the generic Node class
  • +
  • Un-deprecated the CommonmarkConverter::VERSION constant
  • +
  • The Converter constructor now requires an instance of DocParserInterface instead of the concrete DocParser
  • +
  • Changed Emphasis, Strong, and AbstractWebResource to directly extend AbstractInline instead of the (now-deprecated) intermediary AbstractInlineContainer class
  • +
+ +

Fixed

+ +
    +
  • Fixed null errors when inserting sibling Nodes without parents
  • +
  • Fixed NodeWalkerEvent not requiring a Node via its constructor
  • +
  • Fixed Reference::normalizeReference() improperly converting to uppercase instead of performing proper Unicode case-folding
  • +
  • Fixed strong emphasis delimiters not being preserved when enable_strong is set to false (it now works identically to enable_em)
  • +
+ +

Deprecated

+ +
    +
  • Deprecated DocParser::getEnvironment() (you should obtain it some other way)
  • +
  • Deprecated AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
  • +
+ +

Removed

+ +
    +
  • Removed inline processor functionality now that we have proper delimiter support: +
      +
    • Removed addInlineProcessor() from ConfigurableEnvironmentInterface and Environment
    • +
    • Removed getInlineProcessors() from EnvironmentInterface and Environment
    • +
    • Removed EmphasisProcessor
    • +
    • Removed InlineProcessorInterface
    • +
    +
  • +
  • Removed EmphasisParser now that we have proper delimiter support
  • +
  • Removed support for non-UTF-8-compatible encodings +
      +
    • Removed getEncoding() from ContextInterface
    • +
    • Removed getEncoding(), setEncoding(), and $encoding from Context
    • +
    • Removed getEncoding() and the second $encoding constructor param from Cursor
    • +
    +
  • +
  • Removed now-unused methods +
      +
    • Removed DelimiterStack::getTop() (no replacement)
    • +
    • Removed DelimiterStack::iterateByCharacters() (use the new processDelimiters() method instead)
    • +
    • Removed the protected DelimiterStack::findMatchingOpener() method
    • +
    +
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/command-line/index.html b/1.3/command-line/index.html new file mode 100644 index 0000000000..0eb825f4be --- /dev/null +++ b/1.3/command-line/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + Command Line - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Command Line

+ +

Markdown can be converted at the command line using the ./bin/commonmark script.

+ +

Usage

+ +
./bin/commonmark [OPTIONS] [FILE]
+
+ +
    +
  • -h, --help: Shows help and usage information
  • +
  • --enable-em: Disable <em> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --enable-strong: Disable <strong> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --use-asterisk: Disable parsing of * for emphasis by setting to 0; enable with 1 (default: 1)
  • +
  • --use-underscore: Disable parsing of _ for emphasis by setting to 0; enable with 1 (default: 1)
  • +
+ +

If no file is given, input will be read from STDIN.

+ +

Output will be written to STDOUT.

+ +

Examples

+ +

Converting a file named document.md

+ +
./bin/commonmark document.md
+
+ +

Converting a file and saving its output

+ +
./bin/commonmark document.md > output.html
+
+ +

Converting from STDIN

+ +
echo -e '# Hello World!' | ./bin/commonmark
+
+ +

Converting from STDIN and saving the output

+ +
echo -e '# Hello World!' | ./bin/commonmark > output.html
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/configuration/index.html b/1.3/configuration/index.html new file mode 100644 index 0000000000..e2c1df4aae --- /dev/null +++ b/1.3/configuration/index.html @@ -0,0 +1,430 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

You can provide an array of configuration options to the CommonMarkConverter when creating it:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter([
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'enable_em' => true,
+    'enable_strong' => true,
+    'use_asterisk' => true,
+    'use_underscore' => true,
+    'unordered_list_markers' => ['-', '*', '+'],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => INF,
+]);
+
+ +

Here’s a list of currently-supported options:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
  • +
  • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
  • +
  • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
  • +
  • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
  • +
  • unordered_list_markers - Array of characters that can be used to indicated a bulleted list (default: ["-", "*", "+"])
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: infinite). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested. Added in 0.17.
  • +
+ +

Additional configuration options are available for some of the available extensions - refer to their individual documentation for more details.

+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

The Environment also exposes three methods for managing the configuration:

+ +
    +
  • setConfig(array $config = []) - Replace the current configuration with something else
  • +
  • mergeConfig(array $config = []) - Recursively merge the current configuration with the given options
  • +
  • getConfig(string $key, $default = null) - Returns the config value. For nested configs, use a /-separate path; for example: renderer/soft_break
  • +
+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/abstract-syntax-tree/index.html b/1.3/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..9a9efab169 --- /dev/null +++ b/1.3/customization/abstract-syntax-tree/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Traversal

+ +

The following methods can be used to traverse the AST:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

Iteration / Walking the Tree

+ +

If you’d like to iterate through all the nodes, use the walker() method to obtain an instance of NodeWalker. This will walk through the entire tree, emitting NodeWalkerEvents along the way.

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'I am ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

This walker doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes.

+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/block-parsing/index.html b/1.3/customization/block-parsing/index.html new file mode 100644 index 0000000000..94383af563 --- /dev/null +++ b/1.3/customization/block-parsing/index.html @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

Block parsers should implement BlockParserInterface and implement the following method:

+ +

parse()

+ +
public function parse(ContextInterface $context, Cursor $cursor): bool;
+
+ +

When parsing a new line, the DocParser iterates through all registered block parsers and calls their parse() method. Each parser must determine whether it can handle the given line; if so, it should parse the given block and return true.

+ +

Parameters

+ +
    +
  • ContextInterface $context - Provides information about the current context of the DocParser. Includes access to things like the document, current block container, and more.
  • +
  • Cursor $cursor - The Cursor encapsulates the current state of the line being parsed and provides helpers for looking around the current position.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the line will be parsed as text.

+ +

Returning true tells the engine that you’ve successfully parsed the block at the given position. It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of syntax indicating the block start
  2. +
  3. Add the parsed block via $context->addBlock()
  4. +
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible
  • +
  • Your parse() method may be called thousands of times so be sure your code is optimized
  • +
+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

Block elements also play a role during the parsing process as they tell the underlying engine how to handle subsequent blocks that are found.

+ +

AbstractBlockElement Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
canContain(...)Tell the engine whether a subsequent block can be added as a child of yours
isCode()Returns whether this block represents an extra-greedy <code> block
matchesNextLine(...)Returns whether this block continues onto the next line (some blocks are multi-line)
shouldLastLineBeBlank()Returns whether the last line should be blank (primarily used by ListItem elements)
finalize(...)Finalizes the block after all child items have been added, thus marking it as closed for modification
+ +

For examples on how these methods are used, see the core block element classes included with this library.

+ +

AbstractStringContainerBlock

+ +

If your element can contain strings of text, you should extend AbstractStringContainerBlock instead of AbstractBlock. This provides some additional methods needed to manage that inner text:

+ + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
handleRemainingContents(...)This is called when a block has been created but some other text still exists on that line
addLine(...)Adds the given line of text to the block element
getStringContent()Returns the strings contained with that block element
+ +

InlineContainerInterface

+ +

If the text contained by your block should be parsed for inline elements, you should also implement the InlineContainerInterface. This doesn’t add any new methods but does signal to the engine that inline parsing is required.

+ +

Multi-line Code Blocks

+ +

If you have a block which spans multiple lines and doesn’t contain any child blocks, consider having isCode() return true. Code blocks have a special feature which enables “greedy parsing” - once it first parses your block, the engine will assume that most of the subsequent lines of Markdown belong to your block - it won’t try using any other parsers until your parser’s matchesNextLine() method returns false, indicating that we’ve reached the end of that code block.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/block-rendering/index.html b/1.3/customization/block-rendering/index.html new file mode 100644 index 0000000000..c80b1a40db --- /dev/null +++ b/1.3/customization/block-rendering/index.html @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + Block Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Block Rendering

+ +

Block renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement BlockRendererInterface and its render() method:

+ +

render()

+ +
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false);
+
+ +

The HtmlRenderer will call this method whenever a supported block element is encountered in the AST being rendered.

+ +

If the method can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractBlock $block - The encountered block you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to render inlines or easily generate HTML tags
  • +
  • $inTightList = false - Whether the element is being rendered in a tight list or not
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the block and any of its contents. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Block Renderers

+ +

When registering your renderer, you must tell the Environment which block element class your renderer should handle. For example:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the block class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addBlockRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple block types:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Block\Element\IndentedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addBlockRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addBlockRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Node\Block\AbstractBlock;
+use League\CommonMark\Renderer\Block\BlockRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements BlockRendererInterface
+{
+    public function render(AbstractBlock $block, NodeRendererInterface $htmlRenderer, bool $inTightList = false)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addBlockRenderer('League\CommonMark\Block\Element\ThematicBreak', new TextDividerRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any inlines your block might contain!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/cursor/index.html b/1.3/customization/cursor/index.html new file mode 100644 index 0000000000..f1f320ba8b --- /dev/null +++ b/1.3/customization/cursor/index.html @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter()Returns the character at the current position
getCharacter(int $index)Returns the character at the given absolute position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/delimiter-processing/index.html b/1.3/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..90777f3e2c --- /dev/null +++ b/1.3/customization/delimiter-processing/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse);
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/document-processing/index.html b/1.3/customization/document-processing/index.html new file mode 100644 index 0000000000..aee20caf76 --- /dev/null +++ b/1.3/customization/document-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/1.3/customization/environment/index.html b/1.3/customization/environment/index.html new file mode 100644 index 0000000000..b5a2958d4c --- /dev/null +++ b/1.3/customization/environment/index.html @@ -0,0 +1,460 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

A pre-configured Environment can be obtained like this:

+ +
use League\CommonMark;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+ +

All of the core renders, parsers, etc. needed to implement the CommonMark spec will be pre-registered and ready to go.

+ +

You can customize this default Environment (or even a new, empty one) using any of the methods below (from the ConfigurableEnvironmentInterface interface).

+ +

mergeConfig()

+ +
public function mergeConfig(array $config = []);
+
+ +

Merges the given configuration settings into any existing ones.

+ +

setConfig()

+ +
public function setConfig(array $config = []);
+
+ +

Completely replaces the previous configuration settings with the new $config you provide.

+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. This is typically how you’d integrate third-party extensions with this library.

+ +

addBlockParser()

+ +
public function addBlockParser(BlockParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addBlockRenderer()

+ +
public function addBlockRenderer(string $blockClass, BlockRendererInterface $blockRenderer, int $priority = 0);
+
+ +

Registers a BlockRendererInterface to handle a specific type of block ($blockClass) with the given priority (a higher number will be executed earlier).

+ +

See Block Rendering for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addInlineRenderer()

+ +
public function addInlineRenderer(string $inlineClass, InlineRendererInterface $renderer, int $priority = 0);
+
+ +

Registers an InlineRendererInterface to handle a specific type of inline ($inlineClass) with the given priority (a higher number will be executed earlier). +A single renderer can handle multiple inline classes, but you must register it separately for each type. (The same renderer instance can be re-used if desired.)

+ +

See Inline Rendering for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/event-dispatcher/index.html b/1.3/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..85850b97cf --- /dev/null +++ b/1.3/customization/event-dispatcher/index.html @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code. If you’re familiar with Symfony’s EventDispatcher or PSR-14 then this should be very familiar to you.

+ +

Event Class

+ +

All events must extend from the AbstractEvent class:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - an instance of AbstractEvent to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Inline\Element\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event)
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data['attributes']['class'] = 'external-link';
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfig('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = Environment::createCommonMarkEnvironment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convertToHtml($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/extensions/index.html b/1.3/customization/extensions/index.html new file mode 100644 index 0000000000..abcb064d30 --- /dev/null +++ b/1.3/customization/extensions/index.html @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a ConfigurableEnvironmentInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\ConfigurableEnvironmentInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(ConfigurableEnvironmentInterface $environment)
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new EmojiExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/inline-parsing/index.html b/1.3/customization/inline-parsing/index.html new file mode 100644 index 0000000000..8165f1e61c --- /dev/null +++ b/1.3/customization/inline-parsing/index.html @@ -0,0 +1,523 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getCharacters()

+ +

This method should return an array of single characters which the inline parser engine should stop on. When it does find a match in the current line the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has stopped at a matching character; and,
  2. +
  3. No other inline parsers have successfully parsed the character
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser, including the Cursor used to parse the current line.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line/character for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the character will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return ['@'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+        // Save the cursor state in case we need to rewind and bail
+        $previousState = $cursor->saveState();
+        // Advance past the @ symbol to keep parsing simpler
+        $cursor->advance();
+        // Parse the handle
+        $handle = $cursor->match('/^[A-Za-z0-9_]{1,15}(?!\w)/');
+        if (empty($handle)) {
+            // Regex failed to match; this isn't a valid Twitter handle
+            $cursor->restoreState($previousState);
+            return false;
+        }
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Image;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return [':'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // The next character must be a paren; if not, then bail
+        // We use peek() to quickly check without affecting the cursor
+        $nextChar = $cursor->peek();
+        if ($nextChar !== '(' && $nextChar !== ')') {
+            return false;
+        }
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($nextChar === ')') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($nextChar === '(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible.
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/inline-rendering/index.html b/1.3/customization/inline-rendering/index.html new file mode 100644 index 0000000000..a8a673feeb --- /dev/null +++ b/1.3/customization/inline-rendering/index.html @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + Inline Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Inline Rendering

+ +

Inline renderers are responsible for converting the parsed inline elements into their HTML representation.

+ +

All inline renderers should implement InlineRendererInterface and its render() method:

+ +

render()

+ +

Block elements are responsible for calling $htmlRenderer->renderInlines() if they contain inline elements. This in turns causes the HtmlRenderer to call this render() method whenever a supported inline element is encountered.

+ +

If the method can only handle certain inline types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractInline $inline - The encountered inline you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to help escape output or easily generate HTML tags
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the entire inline and any contents. This can be an HtmlElement object (preferred; castable to a string) or a string of raw HTML.

+ +

You are responsible for handling any escaping that may be necessary.

+ +

Return null if your renderer cannot handle the given inline element - the next-highest priority renderer will then be given a chance to render it.

+ +

Designating Inline Renderers

+ +

When registering your render, you must tell the Environment which inline element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the inline class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addInlineRenderer('League\CommonMark\Inline\Element\Link', new MyCustomLinkRenderer());
+
+ +

Example

+ +

Here’s a custom renderer which puts a special class on links to external sites:

+ +
use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+use League\CommonMark\HtmlElement;
+
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+    private $host;
+
+    public function __construct($host)
+    {
+        $this->host = $host;
+    }
+
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline));
+        }
+
+        $attrs = array();
+
+        $attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true);
+
+        if (isset($inline->attributes['title'])) {
+            $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true);
+        }
+
+        if ($this->isExternalUrl($inline->getUrl())) {
+            $attrs['class'] = 'external-link';
+        }
+
+        return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children()));
+    }
+
+    private function isExternalUrl($url)
+    {
+        return parse_url($url, PHP_URL_HOST) !== $this->host;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineRenderer(Link::class, new MyCustomLinkRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Some inlines can contain other inlines - don’t forget to render those too!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/customization/overview/index.html b/1.3/customization/overview/index.html new file mode 100644 index 0000000000..96d6db4e81 --- /dev/null +++ b/1.3/customization/overview/index.html @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

This library uses a three-step process to convert Markdown to HTML:

+ +
    +
  1. Parse the various block and inline elements into an Abstract Syntax Tree (AST)
  2. +
  3. Allow users to iterate and modify the parsed AST
  4. +
  5. Render the final AST representation to HTML
  6. +
+ +

You can hook into any of these three steps to customize this library to suit your needs.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renders convert the parsed blocks/inlines from the AST representation into HTML. There are two types of renderers:

+ + + +

When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you +to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/autolinks/index.html b/1.3/extensions/autolinks/index.html new file mode 100644 index 0000000000..71a9537725 --- /dev/null +++ b/1.3/extensions/autolinks/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

It also provides a parser to autolink @mentions to Twitter, GitHub, or any custom service you wish, though this is disabled by default.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

This extension also provides functionality to automatically link “mentions” like @colinodell to Twitter, GitHub, or any other site of your choice!

+ +

For Twitter:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\InlineMentionParser;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(InlineMentionParser::createTwitterHandleParser());
+
+// TODO: Instantiate your converter and convert some Markdown
+
+ +

For GitHub:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\InlineMentionParser;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(InlineMentionParser::createGithubHandleParser());
+
+// TODO: Instantiate your converter and convert some Markdown
+
+ +

Or configure your own custom one:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\InlineMentionParser;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new InlineMentionParser('https://www.example.com/users/%s/profile'));
+
+// TODO: Instantiate your converter and convert some Markdown
+
+ +

When creating your own, you can provide two parameters to the constructor:

+ +
    +
  • A URL template where %s is replaced with the username (required)
  • +
  • A regular expression to parse and validate the username (optional - defaults to '/^[A-Za-z0-9_]+(?!\w)/')
  • +
+ +

Note that @mention-style linking doesn’t actually require you to add the extension - just the InlineParser of your choice.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/commonmark/index.html b/1.3/extensions/commonmark/index.html new file mode 100644 index 0000000000..840e543393 --- /dev/null +++ b/1.3/extensions/commonmark/index.html @@ -0,0 +1,428 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically included for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Or if you call the Environment::createCommonMarkEnvironment() helper:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\CommonMarkCoreExtension;
+use League\CommonMark\HtmlRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/disallowed-raw-html/index.html b/1.3/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..befb012fa7 --- /dev/null +++ b/1.3/extensions/disallowed-raw-html/index.html @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically filters certain HTML tags when rendering output, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('I cannot change the page <title>anymore</title>');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/external-links/index.html b/1.3/extensions/external-links/index.html new file mode 100644 index 0000000000..4733ec40bf --- /dev/null +++ b/1.3/extensions/external-links/index.html @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Adds a rel="noopener noreferrer" attribute
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Set your configuration
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\HtmlElement;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+
+    /**
+     * @param Link                     $inline
+     * @param ElementRendererInterface $htmlRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . \get_class($inline));
+        }
+
+        if ($inline->getData('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to automagically add an external link icon by targeting the html_class given in the configuration:

+ +
// Font Awesome example:
+a[target="_blank"]::after,
+a.external::after {
+   content: "\f08e";
+   font: normal normal normal 14px/1 FontAwesome;
+}
+
+// Glyphicon example:
+a[target="_blank"]::after,
+a.external::after {
+  @extend .glyphicon;
+  content: "\e164";
+  margin-left: .5em;
+  margin-right: .25em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/github-flavored-markdown/index.html b/1.3/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..8cfbe1797b --- /dev/null +++ b/1.3/extensions/github-flavored-markdown/index.html @@ -0,0 +1,414 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/inlines-only/index.html b/1.3/extensions/inlines-only/index.html new file mode 100644 index 0000000000..84ff7ebd2c --- /dev/null +++ b/1.3/extensions/inlines-only/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+
+// Create a new, empty environment
+$environment = new Environment();
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/overview/index.html b/1.3/extensions/overview/index.html new file mode 100644 index 0000000000..cdb47df467 --- /dev/null +++ b/1.3/extensions/overview/index.html @@ -0,0 +1,490 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
External LinksTags external links with additional markup1.3.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/smart-punctuation/index.html b/1.3/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..bea356e31f --- /dev/null +++ b/1.3/extensions/smart-punctuation/index.html @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Set your configuration
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/strikethrough/index.html b/1.3/extensions/strikethrough/index.html new file mode 100644 index 0000000000..3a2dc1171c --- /dev/null +++ b/1.3/extensions/strikethrough/index.html @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/tables/index.html b/1.3/extensions/tables/index.html new file mode 100644 index 0000000000..212e995518 --- /dev/null +++ b/1.3/extensions/tables/index.html @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Table\TableExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/extensions/task-lists/index.html b/1.3/extensions/task-lists/index.html new file mode 100644 index 0000000000..81a086df44 --- /dev/null +++ b/1.3/extensions/task-lists/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convertToHtml($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/index.html b/1.3/index.html new file mode 100644 index 0000000000..6b23d2e5ca --- /dev/null +++ b/1.3/index.html @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/installation/index.html b/1.3/installation/index.html new file mode 100644 index 0000000000..b33fccf635 --- /dev/null +++ b/1.3/installation/index.html @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +

In your project root just run:

+ +
composer require league/commonmark:^1.3
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^1.3. This is equivalent to >=1.3 <2.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/security/index.html b/1.3/security/index.html new file mode 100644 index 0000000000..505feed27b --- /dev/null +++ b/1.3/security/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convertToHtml($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.3/upgrading/index.html b/1.3/upgrading/index.html new file mode 100644 index 0000000000..742ba1efcc --- /dev/null +++ b/1.3/upgrading/index.html @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.x - 1.3 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.3. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 1.x to 1.3

+ +

There are no breaking changes introduced in 1.3, but we did move most of our extensions into the core library.

+ +

GitHub-Flavored Markdown and other official extensions

+ +

Previously, anyone wanting GFM support had to install additional libraries like league/commonmark-extras. This is no longer required as GFM support is now built into this library! This is also true for other official extensions like league/commonmark-ext-inlines-only.

+ +

If you were previously using any of the league/commonmark-ext* libraries:

+ +
    +
  • Upgrade to league/commonmark 1.3+
  • +
  • Replace any reference to the old extensions in your code with the new ones +
      +
    • (In most cases, simply search your code for League\CommonMark\Ext\ and replace it with League\CommonMark\Extension\)
    • +
    +
  • +
  • Remove those league/commonmark-ext* dependencies from Composer
  • +
+ +

See the GitHub-Flavored Markdown extension documentation for more information on using this extension. Additional details can also be found in Colin O’Dell’s blog post.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/basic-usage/index.html b/1.4/basic-usage/index.html new file mode 100644 index 0000000000..c72b9c6667 --- /dev/null +++ b/1.4/basic-usage/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

Additional customization is also possible, and we have many handy extensions to enable additional syntax and features.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/changelog/index.html b/1.4/changelog/index.html new file mode 100644 index 0000000000..2e17058df5 --- /dev/null +++ b/1.4/changelog/index.html @@ -0,0 +1,1056 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 1.x releases are shown below. See the full list of releases for the complete changelog.

+ +

1.6.7 - 2022-01-13

+ +

Changed

+ +
    +
  • Added ReturnTypeWillChange attribute to prevent PHP 8.1 deprecation warnings (#785)
  • +
  • Coerced punctuation counts to integers to ensure floats are never used
  • +
+ +

1.6.6 - 2021-07-17

+ +

Fixed

+ +
    +
  • Fixed Mentions inside of links creating nested links against the spec’s rules (#688)
  • +
+ +

1.6.5 - 2021-06-26

+ +

Changed

+ +
    +
  • Simplified checks for thematic breaks
  • +
+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not handling autolinks by adjusting its priority to -50 (#681)
  • +
+ +

1.6.4 - 2021-06-19

+ +

Changed

+ +
    +
  • Optimized attribute parsing to avoid inspecting every space character (30% performance boost)
  • +
+ +

1.6.3 - 2021-06-19

+ +

Fixed

+ +
    +
  • Fixed incorrect parsing of tilde-fenced code blocks with leading spaces (#676)
  • +
+ +

1.6.2 - 2021-05-12

+ +

Fixed

+ +
    +
  • Fixed incorrect error level for deprecation notices
  • +
+ +

1.6.1 - 2021-05-08

+ +

Fixed

+ +
    +
  • Fixed HeadingPermalinkProcessor skipping text contents from certain nodes (#615)
  • +
+ +

1.6.0 - 2021-05-01

+ +

Please see https://commonmark.thephpleague.com/1.6/upgrading/ for important information about this release and the upcoming 2.0.0 version.

+ +

Added

+ +
    +
  • Added forward-compatibility for configuration options which will be changing in 2.0: +
      +
    • commonmark/enable_em (currently enable_em in 1.x)
    • +
    • commonmark/enable_strong (currently enable_strong in 1.x)
    • +
    • commonmark/use_asterisk (currently use_asterisk in 1.x)
    • +
    • commonmark/use_underscore (currently use_underscore in 1.x)
    • +
    • commonmark/unordered_list_markers (currently unordered_list_markers in 1.x)
    • +
    • mentions/*/prefix (currently mentions/*/symbol in 1.x)
    • +
    • mentions/*/pattern (currently mentions/*/regex in 1.x)
    • +
    • max_nesting_level (currently supports int and float values in 1.x; will only support int in 2.0)
    • +
    +
  • +
  • Added new MarkdownConverter class for creating converters with custom environments; this replaces the previously-deprecated Converter class
  • +
  • Added new RegexHelper::matchFirst() method
  • +
  • Added new Configuration::exists() method
  • +
+ +

Changed

+ +
    +
  • The max_nesting_level option now defaults to PHP_INT_MAX instead of INF
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the configuration options shown above
  • +
  • Deprecated the ability to pass a custom Environment into the constructors of CommonMarkConverter and GithubFlavoredMarkdownConverter; use MarkdownConverter instead
  • +
  • Deprecated ConfigurableEnvironmentInterface::setConfig(); use mergeConfig() instead
  • +
  • Deprecated calling ConfigurableEnvironmentInterface::mergeConfig() without any parameters
  • +
  • Deprecated calling Configuration::get() and EnvironmentInterface::getConfig() without any parameters
  • +
  • Deprecated calling Configuration::set() without the second $value parameter
  • +
  • Deprecated RegexHelper::matchAll(); use RegexHelper::matchFirst() instead
  • +
  • Deprecated extending the ArrayCollection class; will be marked final in 2.0
  • +
+ +

Fixed

+ +
    +
  • Fixed missing check for empty arrays being passed into the unordered_list_markers configuration option
  • +
+ +

1.5.8 - 2021-03-28

+ +

Fixed

+ +
    +
  • Fixed Table of Contents not rendering heading inlines properly (#587, #588)
  • +
  • Fixed parsing of tables within list items (#590)
  • +
+ +

1.5.7 - 2020-10-31

+ +

Fixed

+ +
    +
  • Fixed mentions not being parsed when appearing after non-word characters (#582)
  • +
+ +

1.5.6 - 2020-10-17

+ +

Changed

+ +
    +
  • Blocks added outside of the parsing context now have their start/end line numbers defaulted to 0 to avoid type errors (#579)
  • +
+ +

Fixed

+ +
    +
  • Fixed replacement blocks not inheriting the start line number of the container they’re replacing (#579)
  • +
  • Fixed Table of Contents blocks not having correct start/end line numbers (#579)
  • +
+ +

1.5.5 - 2020-09-13

+ +

Changed

+ +
    +
  • Bumped CommonMark spec compliance to 0.28.2
  • +
+ +

Fixed

+ +
    +
  • Fixed textarea elements not being treated as a type 1 HTML block (like script, style, or pre)
  • +
  • Fixed autolink processor not handling other unmatched trailing parentheses
  • +
+ +

1.5.4 - 2020-08-18

+ +

Fixed

+ +
    +
  • Fixed footnote ID configuration not taking effect (#524, #530)
  • +
  • Fixed heading permalink slugs not being unique (#531, #534)
  • +
+ +

1.5.3 - 2020-07-19

+ +

Fixed

+ +
    +
  • Fixed regression of multi-byte inline parser characters not being matched
  • +
+ +

1.5.2 - 2020-07-19

+ +

Changed

+ +
    +
  • Significantly improved performance of the inline parser regex
  • +
+ +

Fixed

+ +
    +
  • Fixed parent class lookups for non-existent classes on PHP 8 (#517)
  • +
+ +

1.5.1 - 2020-06-27

+ +

Fixed

+ +
    +
  • Fixed UTF-8 encoding not being checked in the UrlEncoder utility (#509) or the Cursor
  • +
+ +

1.5.0 - 2020-06-21

+ +

Added

+ +
    +
  • Added new AttributesExtension based on https://github.com/webuni/commonmark-attributes-extension (#474)
  • +
  • Added new FootnoteExtension based on https://github.com/rezozero/commonmark-ext-footnotes (#474)
  • +
  • Added new MentionExtension to replace InlineMentionParser with more flexibility and customization
  • +
  • Added the ability to render TableOfContents nodes anywhere in a document (given by a placeholder)
  • +
  • Added the ability to properly clone Node objects
  • +
  • Added options to customize the value of rel attributes set via the ExternalLink extension (#476)
  • +
  • Added a new heading_permalink/slug_normalizer configuration option to allow custom slug generation (#460)
  • +
  • Added a new heading_permalink/symbol configuration option to replace the now deprecated heading_permalink/inner_contents configuration option (#505)
  • +
  • Added SlugNormalizer and TextNormalizer classes to make normalization reusable by extensions (#485)
  • +
  • Added new classes: +
      +
    • TableOfContentsGenerator
    • +
    • TableOfContentsGeneratorInterface
    • +
    • TableOfContentsPlaceholder
    • +
    • TableOfContentsPlaceholderParser
    • +
    • TableOfContentsPlaceholderRenderer
    • +
    +
  • +
+ +

Changed

+ +
    +
  • “Moved” the TableOfContents class into a new Node sub-namespace (with backward-compatibility)
  • +
  • Reference labels are now generated and stored in lower-case instead of upper-case
  • +
  • Reference labels are no longer normalized inside the Reference, only the ReferenceMap
  • +
+ +

Fixed

+ +
    +
  • Fixed reference label case folding polyfill not being consistent between different PHP versions
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the CommonMarkConverter::VERSION constant (#496)
  • +
  • Deprecated League\CommonMark\Extension\Autolink\InlineMentionParser (use League\CommonMark\Extension\Mention\MentionParser instead)
  • +
  • Deprecated everything under League\CommonMark\Extension\HeadingPermalink\Slug (use the classes under League\CommonMark\Normalizer instead)
  • +
  • Deprecated League\CommonMark\Extension\TableOfContents\TableOfContents (use the one in the new Node sub-namespace instead)
  • +
  • Deprecated the STYLE_ and NORMALIZE_ constants in TableOfContentsBuilder (use the ones in TableOfContentsGenerator instead)
  • +
  • Deprecated the \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant (#505)
  • +
  • Deprecated the heading_permalink/inner_contents configuration option in the HeadingPermalink extension (use the new heading_permalink/symbol configuration option instead) (#505)
  • +
+ +

1.4.3 - 2020-05-04

+ +

Fixed

+ +
    +
  • Fixed certain Unicode letters, numbers, and marks not being preserved when generating URL slugs (#467)
  • +
+ +

1.4.2 - 2020-04-24

+ +

Fixed

+ +
    +
  • Fixed inline code blocks not be included within heading permalinks (#457)
  • +
+ +

1.4.1 - 2020-04-20

+ +

Fixed

+ +
    +
  • Fixed BC break caused by ConverterInterface alias not being used by some DI containers (#454)
  • +
+ +

1.4.0 - 2020-04-18

+ +

Added

+ +
    +
  • Added new Heading Permalink extension (#420)
  • +
  • Added new Table of Contents extension (#441)
  • +
  • Added new MarkdownConverterInterface as a long-term replacement for ConverterInterface (#439)
  • +
  • Added new DocumentPreParsedEvent event (#427, #359, #399)
  • +
  • Added new ListBlock::TYPE_BULLET constant as a replacement for ListBlock::TYPE_UNORDERED
  • +
  • Added new MarkdownInput class and MarkdownInputInterface to handle pre-parsing and allow listeners to replace Markdown contents
  • +
+ +

Changed

+ +
    +
  • Block & inline renderers will now render child classes automatically (#222, #209)
  • +
  • The ListBlock constants now use fully-lowercased values instead of titlecased values
  • +
  • Significantly improved typing
  • +
+ +

Fixed

+ +
    +
  • Fixed loose comparison when checking for table alignment
  • +
  • Fixed StaggeredDelimiterProcessor returning from a void function
  • +
+ +

Deprecated

+ +
    +
  • The Converter class has been deprecated; use CommonMarkConverter instead (#438, #439)
  • +
  • The ConverterInterface has been deprecated; use MarkdownConverterInterface instead (#438, #439)
  • +
  • The bin/commonmark script has been deprecated
  • +
  • The following methods of ArrayCollection have been deprecated: +
      +
    • add()
    • +
    • set()
    • +
    • get()
    • +
    • remove()
    • +
    • isEmpty()
    • +
    • contains()
    • +
    • indexOf()
    • +
    • containsKey()
    • +
    • replaceWith()
    • +
    • removeGaps()
    • +
    +
  • +
  • The ListBlock::TYPE_UNORDERED constant has been deprecated, use ListBlock::TYPE_BULLET instead
  • +
+ +

1.3.4 - 2020-04-13

+ +

Fixed

+ +
    +
  • Fixed configuration/environment not being injected into event listeners when adding them via [$instance, 'method'] callable syntax (#440)
  • +
+ +

1.3.3 - 2020-04-05

+ +

Fixed

+ +
    +
  • Fixed event listeners not having the environment or configuration injected if they implemented the EnvironmentAwareInterface or ConfigurationAwareInterface (#423)
  • +
+ +

1.3.2 - 2020-03-25

+ +

Fixed

+ +
    +
  • Optimized URL normalization in cases where URLs don’t contain special characters (#417, #418)
  • +
+ +

1.3.1 - 2020-02-28

+ +

Fixed

+ +
    +
  • Fixed return types of Environment::createCommonMarkEnvironment() and Environment::createGFMEnvironment()
  • +
+ +

1.3.0 - 2020-02-09

+ +

ℹ️ Do you use league/commonmark-ext* packages? Those features are now included directly in this library! See #409 for details on making the switch.

+ +

Added

+ +
    +
  • Added (optional) full GFM support! 🎉🎉🎉 (#409)
  • +
  • Added check to ensure Markdown input is valid UTF-8 (#401, #405)
  • +
  • Added new unordered_list_markers configuration option (#408, #411)
  • +
+ +

Changed

+ +
    +
  • Introduced several micro-optimizations for a 5-10% performance boost
  • +
+ +

1.2.2 - 2020-01-16

+ +

This release contains the same changes as 1.1.3:

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.1.3 - 2020-01-16

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.2.1 - 2020-01-15

+ +

Changed

+ +
    +
  • Introduced several micro-optimizations, reducing the parse time by 8%
  • +
+ +

1.2.0 - 2020-01-09

+ +

Changed

+ +
    +
  • Removed URL decoding step before encoding (more performant and better matches the JS library)
  • +
  • Removed redundant token from HTML tag regex
  • +
+ +

1.1.2 - 2019-12-10

+ +

Fixed

+ +
    +
  • Fixed URL normalization not handling non-UTF-8 sequences properly (#395, #396)
  • +
+ +

1.1.1 - 2019-11-11

+ +

Fixed

+ +
    +
  • Fixed handling of link destinations with unbalanced unescaped parens
  • +
  • Fixed adding delimiters to stack which can neither open nor close a run
  • +
+ +

1.1.0 - 2019-10-31

+ +

Added

+ +
    +
  • Added a new Html5EntityDecoder class (#387)
  • +
+ +

Changed

+ +
    +
  • Improved performance by 10% (#389)
  • +
  • Made entity decoding less memory-intensive (#386, #387)
  • +
+ +

Fixed

+ +
    +
  • Fixed PHP 7.4 compatibility issues
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the Html5Entities class - use Html5EntityDecoder instead (#387)
  • +
+ +

1.0.0 - 2019-06-29

+ +

First stable release! 🎉

+ +

No code changes have been introduced since 1.0.0-rc1

+ +

1.0.0-rc1 - 2019-06-20

+ +

Added

+ +
    +
  • Extracted a ReferenceMapInterface from the ReferenceMap class
  • +
  • Added optional ReferenceMapInterface parameter to the Document constructor
  • +
+ +

Changed

+ +
    +
  • Replaced all references to ReferenceMap with ReferenceMapInterface
  • +
  • ReferenceMap::addReference() no longer returns $this
  • +
+ +

Fixed

+ +
    +
  • Fixed bug where elements with content of "0" wouldn’t be rendered (#376)
  • +
+ +

1.0.0-beta4 - 2019-06-05

+ +

Added

+ +
    +
  • Added event dispatcher functionality (#359, #372)
  • +
+ +

Removed

+ +
    +
  • Removed DocumentProcessorInterface functionality in favor of event dispatching (#373)
  • +
+ +

1.0.0-beta3 - 2019-05-28

+ +

Changed

+ +
    +
  • Made the Delimiter class final and extracted a new DelimiterInterface +
      +
    • Modified most external usages to use this new interface
    • +
    +
  • +
  • Renamed three Delimiter methods: +
      +
    • getOrigDelims() renamed to getOriginalLength()
    • +
    • getNumDelims() renamed to getLength()
    • +
    • setNumDelims() renamed to setLength()
    • +
    +
  • +
  • Made additional classes final: +
      +
    • DelimiterStack
    • +
    • ReferenceMap
    • +
    • ReferenceParser
    • +
    +
  • +
  • Moved ReferenceParser into the Reference sub-namespace
  • +
+ +

Removed

+ +
    +
  • Removed unused Delimiter methods: +
      +
    • setCanOpen()
    • +
    • setCanClose()
    • +
    • setChar()
    • +
    • setIndex()
    • +
    • setInlineNode()
    • +
    +
  • +
  • Removed fluent interface from Delimiter (setter methods now have no return values)
  • +
+ +

1.0.0-beta2 - 2019-05-27

+ +

This beta release fixes a couple of items that were not addressed in the previous beta.

+ +

Changed

+ +
    +
  • DelimiterProcessorInterface::process() will accept any type of AbstractStringContainer now, not just Text nodes
  • +
  • The Delimiter constructor, getInlineNode(), and setInlineNode() no longer accept generic Node elements - only AbstractStringContainers
  • +
+ +

Removed

+ +
    +
  • Removed all deprecated functionality: +
      +
    • The safe option (use html_input and allow_unsafe_links options instead)
    • +
    • All deprecated RegexHelper constants
    • +
    • DocParser::getEnvironment() (you should obtain it some other way)
    • +
    • AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
    • +
    +
  • +
+ +

1.0.0-beta1 - 2019-05-26

+ +

See the upgrading guide for additional information.

+ +

Added

+ +
    +
  • Added proper support for delimiters, including custom delimiters +
      +
    • addDelimiterProcessor() added to ConfigurableEnvironmentInterface and Environment
    • +
    +
  • +
  • Basic delimiters no longer need custom parsers - they’ll be parsed automatically
  • +
  • Added new methods: +
      +
    • AdjacentTextMerger::mergeTextNodesBetweenExclusive()
    • +
    • CommonMarkConveter::getEnvironment()
    • +
    • Configuration::set()
    • +
    +
  • +
  • Extracted some new interfaces from base classes: +
      +
    • DocParserInterface created from DocParser
    • +
    • ConfigurationInterface created from Configuration
    • +
    • ReferenceInterface created from Reference
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Renamed several methods of the Configuration class: +
      +
    • getConfig() renamed to get()
    • +
    • mergeConfig() renamed to merge()
    • +
    • setConfig() renamed to replace()
    • +
    +
  • +
  • Changed ConfigurationAwareInterface::setConfiguration() to accept the new ConfigurationInterface instead of the concrete class
  • +
  • Renamed the AdjoiningTextCollapser class to AdjacentTextMerger +
      +
    • Replaced its collapseTextNodes() method with the new mergeChildNodes() method
    • +
    +
  • +
  • Made several classes final: +
      +
    • Configuration
    • +
    • DocParser
    • +
    • HtmlRenderer
    • +
    • InlineParserEngine
    • +
    • NodeWalker
    • +
    • Reference
    • +
    • All of the block/inline parsers and renderers
    • +
    +
  • +
  • Reduced visibility of several internal methods to private: +
      +
    • DelimiterStack::findEarliest()
    • +
    • All protected methods in InlineParserEngine
    • +
    +
  • +
  • Marked some classes and methods as @internal
  • +
  • ElementRendererInterface now requires a public renderInline() method; added this to HtmlRenderer
  • +
  • Changed InlineParserEngine::parse() to require an AbstractStringContainerBlock instead of the generic Node class
  • +
  • Un-deprecated the CommonmarkConverter::VERSION constant
  • +
  • The Converter constructor now requires an instance of DocParserInterface instead of the concrete DocParser
  • +
  • Changed Emphasis, Strong, and AbstractWebResource to directly extend AbstractInline instead of the (now-deprecated) intermediary AbstractInlineContainer class
  • +
+ +

Fixed

+ +
    +
  • Fixed null errors when inserting sibling Nodes without parents
  • +
  • Fixed NodeWalkerEvent not requiring a Node via its constructor
  • +
  • Fixed Reference::normalizeReference() improperly converting to uppercase instead of performing proper Unicode case-folding
  • +
  • Fixed strong emphasis delimiters not being preserved when enable_strong is set to false (it now works identically to enable_em)
  • +
+ +

Deprecated

+ +
    +
  • Deprecated DocParser::getEnvironment() (you should obtain it some other way)
  • +
  • Deprecated AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
  • +
+ +

Removed

+ +
    +
  • Removed inline processor functionality now that we have proper delimiter support: +
      +
    • Removed addInlineProcessor() from ConfigurableEnvironmentInterface and Environment
    • +
    • Removed getInlineProcessors() from EnvironmentInterface and Environment
    • +
    • Removed EmphasisProcessor
    • +
    • Removed InlineProcessorInterface
    • +
    +
  • +
  • Removed EmphasisParser now that we have proper delimiter support
  • +
  • Removed support for non-UTF-8-compatible encodings +
      +
    • Removed getEncoding() from ContextInterface
    • +
    • Removed getEncoding(), setEncoding(), and $encoding from Context
    • +
    • Removed getEncoding() and the second $encoding constructor param from Cursor
    • +
    +
  • +
  • Removed now-unused methods +
      +
    • Removed DelimiterStack::getTop() (no replacement)
    • +
    • Removed DelimiterStack::iterateByCharacters() (use the new processDelimiters() method instead)
    • +
    • Removed the protected DelimiterStack::findMatchingOpener() method
    • +
    +
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/command-line/index.html b/1.4/command-line/index.html new file mode 100644 index 0000000000..b6eaab0b18 --- /dev/null +++ b/1.4/command-line/index.html @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + Command Line - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Command Line

+ +

This functionality has been deprecated in version 1.4 and will be removed in 2.0.

+ +

Markdown can be converted at the command line using the ./bin/commonmark script.

+ +

Usage

+ +
./bin/commonmark [OPTIONS] [FILE]
+
+ +
    +
  • -h, --help: Shows help and usage information
  • +
  • --enable-em: Disable <em> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --enable-strong: Disable <strong> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --use-asterisk: Disable parsing of * for emphasis by setting to 0; enable with 1 (default: 1)
  • +
  • --use-underscore: Disable parsing of _ for emphasis by setting to 0; enable with 1 (default: 1)
  • +
+ +

If no file is given, input will be read from STDIN.

+ +

Output will be written to STDOUT.

+ +

Examples

+ +

Converting a file named document.md

+ +
./bin/commonmark document.md
+
+ +

Converting a file and saving its output

+ +
./bin/commonmark document.md > output.html
+
+ +

Converting from STDIN

+ +
echo -e '# Hello World!' | ./bin/commonmark
+
+ +

Converting from STDIN and saving the output

+ +
echo -e '# Hello World!' | ./bin/commonmark > output.html
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/configuration/index.html b/1.4/configuration/index.html new file mode 100644 index 0000000000..ab8f913dac --- /dev/null +++ b/1.4/configuration/index.html @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

You can provide an array of configuration options to the CommonMarkConverter when creating it:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter([
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'enable_em' => true,
+    'enable_strong' => true,
+    'use_asterisk' => true,
+    'use_underscore' => true,
+    'unordered_list_markers' => ['-', '*', '+'],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => INF,
+]);
+
+ +

Here’s a list of currently-supported options:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
  • +
  • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
  • +
  • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
  • +
  • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
  • +
  • unordered_list_markers - Array of characters that can be used to indicated a bulleted list (default: ["-", "*", "+"])
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: infinite). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested. Added in 0.17.
  • +
+ +

Additional configuration options are available for some of the available extensions - refer to their individual documentation for more details.

+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

The Environment also exposes three methods for managing the configuration:

+ +
    +
  • setConfig(array $config = []) - Replace the current configuration with something else
  • +
  • mergeConfig(array $config = []) - Recursively merge the current configuration with the given options
  • +
  • getConfig(string $key, $default = null) - Returns the config value. For nested configs, use a /-separate path; for example: renderer/soft_break
  • +
+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/abstract-syntax-tree/index.html b/1.4/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..bbb0aef252 --- /dev/null +++ b/1.4/customization/abstract-syntax-tree/index.html @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ + + +

Traversal

+ +

The following methods can be used to traverse the AST:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

Iteration / Walking the Tree

+ +

If you’d like to iterate through all the nodes, use the walker() method to obtain an instance of NodeWalker. This will walk through the entire tree, emitting NodeWalkerEvents along the way.

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'I am ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

This walker doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes.

+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/block-parsing/index.html b/1.4/customization/block-parsing/index.html new file mode 100644 index 0000000000..fad65c8ebb --- /dev/null +++ b/1.4/customization/block-parsing/index.html @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

Block parsers should implement BlockParserInterface and implement the following method:

+ +

parse()

+ +
public function parse(ContextInterface $context, Cursor $cursor): bool;
+
+ +

When parsing a new line, the DocParser iterates through all registered block parsers and calls their parse() method. Each parser must determine whether it can handle the given line; if so, it should parse the given block and return true.

+ +

Parameters

+ +
    +
  • ContextInterface $context - Provides information about the current context of the DocParser. Includes access to things like the document, current block container, and more.
  • +
  • Cursor $cursor - The Cursor encapsulates the current state of the line being parsed and provides helpers for looking around the current position.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the line will be parsed as text.

+ +

Returning true tells the engine that you’ve successfully parsed the block at the given position. It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of syntax indicating the block start
  2. +
  3. Add the parsed block via $context->addBlock()
  4. +
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible
  • +
  • Your parse() method may be called thousands of times so be sure your code is optimized
  • +
+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

Block elements also play a role during the parsing process as they tell the underlying engine how to handle subsequent blocks that are found.

+ +

AbstractBlockElement Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
canContain(...)Tell the engine whether a subsequent block can be added as a child of yours
isCode()Returns whether this block represents an extra-greedy <code> block
matchesNextLine(...)Returns whether this block continues onto the next line (some blocks are multi-line)
shouldLastLineBeBlank()Returns whether the last line should be blank (primarily used by ListItem elements)
finalize(...)Finalizes the block after all child items have been added, thus marking it as closed for modification
+ +

For examples on how these methods are used, see the core block element classes included with this library.

+ +

AbstractStringContainerBlock

+ +

If your element can contain strings of text, you should extend AbstractStringContainerBlock instead of AbstractBlock. This provides some additional methods needed to manage that inner text:

+ + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
handleRemainingContents(...)This is called when a block has been created but some other text still exists on that line
addLine(...)Adds the given line of text to the block element
getStringContent()Returns the strings contained with that block element
+ +

InlineContainerInterface

+ +

If the text contained by your block should be parsed for inline elements, you should also implement the InlineContainerInterface. This doesn’t add any new methods but does signal to the engine that inline parsing is required.

+ +

Multi-line Code Blocks

+ +

If you have a block which spans multiple lines and doesn’t contain any child blocks, consider having isCode() return true. Code blocks have a special feature which enables “greedy parsing” - once it first parses your block, the engine will assume that most of the subsequent lines of Markdown belong to your block - it won’t try using any other parsers until your parser’s matchesNextLine() method returns false, indicating that we’ve reached the end of that code block.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/block-rendering/index.html b/1.4/customization/block-rendering/index.html new file mode 100644 index 0000000000..b2b6371ceb --- /dev/null +++ b/1.4/customization/block-rendering/index.html @@ -0,0 +1,471 @@ + + + + + + + + + + + + + + + + + Block Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Block Rendering

+ +

Block renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement BlockRendererInterface and its render() method:

+ +

render()

+ +
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false);
+
+ +

The HtmlRenderer will call this method whenever a supported block element is encountered in the AST being rendered.

+ +

If the method can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractBlock $block - The encountered block you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to render inlines or easily generate HTML tags
  • +
  • $inTightList = false - Whether the element is being rendered in a tight list or not
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the block and any of its contents. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Block Renderers

+ +

When registering your renderer, you must tell the Environment which block element class your renderer should handle. For example:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the block class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addBlockRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple block types:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Block\Element\IndentedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addBlockRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addBlockRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Node\Block\AbstractBlock;
+use League\CommonMark\Renderer\Block\BlockRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements BlockRendererInterface
+{
+    public function render(AbstractBlock $block, NodeRendererInterface $htmlRenderer, bool $inTightList = false)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addBlockRenderer('League\CommonMark\Block\Element\ThematicBreak', new TextDividerRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any inlines your block might contain!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/cursor/index.html b/1.4/customization/cursor/index.html new file mode 100644 index 0000000000..ad0a791e86 --- /dev/null +++ b/1.4/customization/cursor/index.html @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter()Returns the character at the current position
getCharacter(int $index)Returns the character at the given absolute position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/delimiter-processing/index.html b/1.4/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..4116747c11 --- /dev/null +++ b/1.4/customization/delimiter-processing/index.html @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse);
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/document-processing/index.html b/1.4/customization/document-processing/index.html new file mode 100644 index 0000000000..88af653ef9 --- /dev/null +++ b/1.4/customization/document-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/1.4/customization/environment/index.html b/1.4/customization/environment/index.html new file mode 100644 index 0000000000..e660ec8fbf --- /dev/null +++ b/1.4/customization/environment/index.html @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

A pre-configured Environment can be obtained like this:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+ +

All of the core renders, parsers, etc. needed to implement the CommonMark spec will be pre-registered and ready to go.

+ +

You can customize this default Environment (or even a new, empty one) using any of the methods below (from the ConfigurableEnvironmentInterface interface).

+ +

mergeConfig()

+ +
public function mergeConfig(array $config = []);
+
+ +

Merges the given configuration settings into any existing ones.

+ +

setConfig()

+ +
public function setConfig(array $config = []);
+
+ +

Completely replaces the previous configuration settings with the new $config you provide.

+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. This is typically how you’d integrate third-party extensions with this library.

+ +

addBlockParser()

+ +
public function addBlockParser(BlockParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addBlockRenderer()

+ +
public function addBlockRenderer(string $blockClass, BlockRendererInterface $blockRenderer, int $priority = 0);
+
+ +

Registers a BlockRendererInterface to handle a specific type of block ($blockClass) with the given priority (a higher number will be executed earlier).

+ +

See Block Rendering for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addInlineRenderer()

+ +
public function addInlineRenderer(string $inlineClass, InlineRendererInterface $renderer, int $priority = 0);
+
+ +

Registers an InlineRendererInterface to handle a specific type of inline ($inlineClass) with the given priority (a higher number will be executed earlier). +A single renderer can handle multiple inline classes, but you must register it separately for each type. (The same renderer instance can be re-used if desired.)

+ +

See Inline Rendering for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/event-dispatcher/index.html b/1.4/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..cf20ce5719 --- /dev/null +++ b/1.4/customization/event-dispatcher/index.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code. If you’re familiar with Symfony’s EventDispatcher or PSR-14 then this should be very familiar to you.

+ +

Event Class

+ +

All events must extend from the AbstractEvent class:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - an instance of AbstractEvent to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Inline\Element\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event)
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data['attributes']['class'] = 'external-link';
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfig('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = Environment::createCommonMarkEnvironment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convertToHtml($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/extensions/index.html b/1.4/customization/extensions/index.html new file mode 100644 index 0000000000..6f1b5ce01d --- /dev/null +++ b/1.4/customization/extensions/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a ConfigurableEnvironmentInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\ConfigurableEnvironmentInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(ConfigurableEnvironmentInterface $environment)
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new EmojiExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/inline-parsing/index.html b/1.4/customization/inline-parsing/index.html new file mode 100644 index 0000000000..fc2d66dfde --- /dev/null +++ b/1.4/customization/inline-parsing/index.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getCharacters()

+ +

This method should return an array of single characters which the inline parser engine should stop on. When it does find a match in the current line the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has stopped at a matching character; and,
  2. +
  3. No other inline parsers have successfully parsed the character
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser, including the Cursor used to parse the current line.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line/character for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the character will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return ['@'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+        // Save the cursor state in case we need to rewind and bail
+        $previousState = $cursor->saveState();
+        // Advance past the @ symbol to keep parsing simpler
+        $cursor->advance();
+        // Parse the handle
+        $handle = $cursor->match('/^[A-Za-z0-9_]{1,15}(?!\w)/');
+        if (empty($handle)) {
+            // Regex failed to match; this isn't a valid Twitter handle
+            $cursor->restoreState($previousState);
+            return false;
+        }
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Image;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return [':'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // The next character must be a paren; if not, then bail
+        // We use peek() to quickly check without affecting the cursor
+        $nextChar = $cursor->peek();
+        if ($nextChar !== '(' && $nextChar !== ')') {
+            return false;
+        }
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($nextChar === ')') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($nextChar === '(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible.
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/inline-rendering/index.html b/1.4/customization/inline-rendering/index.html new file mode 100644 index 0000000000..d3797e1d21 --- /dev/null +++ b/1.4/customization/inline-rendering/index.html @@ -0,0 +1,471 @@ + + + + + + + + + + + + + + + + + Inline Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Inline Rendering

+ +

Inline renderers are responsible for converting the parsed inline elements into their HTML representation.

+ +

All inline renderers should implement InlineRendererInterface and its render() method:

+ +

render()

+ +

Block elements are responsible for calling $htmlRenderer->renderInlines() if they contain inline elements. This in turns causes the HtmlRenderer to call this render() method whenever a supported inline element is encountered.

+ +

If the method can only handle certain inline types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractInline $inline - The encountered inline you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to help escape output or easily generate HTML tags
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the entire inline and any contents. This can be an HtmlElement object (preferred; castable to a string) or a string of raw HTML.

+ +

You are responsible for handling any escaping that may be necessary.

+ +

Return null if your renderer cannot handle the given inline element - the next-highest priority renderer will then be given a chance to render it.

+ +

Designating Inline Renderers

+ +

When registering your render, you must tell the Environment which inline element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the inline class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addInlineRenderer('League\CommonMark\Inline\Element\Link', new MyCustomLinkRenderer());
+
+ +

Example

+ +

Here’s a custom renderer which puts a special class on links to external sites:

+ +
use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+use League\CommonMark\HtmlElement;
+
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+    private $host;
+
+    public function __construct($host)
+    {
+        $this->host = $host;
+    }
+
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline));
+        }
+
+        $attrs = array();
+
+        $attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true);
+
+        if (isset($inline->attributes['title'])) {
+            $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true);
+        }
+
+        if ($this->isExternalUrl($inline->getUrl())) {
+            $attrs['class'] = 'external-link';
+        }
+
+        return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children()));
+    }
+
+    private function isExternalUrl($url)
+    {
+        return parse_url($url, PHP_URL_HOST) !== $this->host;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineRenderer(Link::class, new MyCustomLinkRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Some inlines can contain other inlines - don’t forget to render those too!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/customization/overview/index.html b/1.4/customization/overview/index.html new file mode 100644 index 0000000000..9c5b39bf6c --- /dev/null +++ b/1.4/customization/overview/index.html @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders you need
  2. +
  3. Set custom configuration options within the Environment
  4. +
  5. Instantiate a DocParser and HtmlRenderer using that Environment
  6. +
  7. Use the DocParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  8. +
  9. Use the HtmlRenderer to convert the AST Document into HTML
  10. +
+ +

CommonMarkConverter handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->setConfig([
+    'html_input' => 'strip',
+]);
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renders convert the parsed blocks/inlines from the AST representation into HTML. There are two types of renderers:

+ + + +

When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you +to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/autolinks/index.html b/1.4/extensions/autolinks/index.html new file mode 100644 index 0000000000..7f4853e2c5 --- /dev/null +++ b/1.4/extensions/autolinks/index.html @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

It also provides a parser to autolink @mentions to Twitter, GitHub, or any custom service you wish, though this is disabled by default.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

This extension also provides functionality to automatically link “mentions” like @colinodell to Twitter, GitHub, or any other site of your choice!

+ +

For Twitter:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\InlineMentionParser;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(InlineMentionParser::createTwitterHandleParser());
+
+// TODO: Instantiate your converter and convert some Markdown
+
+ +

For GitHub:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\InlineMentionParser;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(InlineMentionParser::createGithubHandleParser());
+
+// TODO: Instantiate your converter and convert some Markdown
+
+ +

Or configure your own custom one:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\InlineMentionParser;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new InlineMentionParser('https://www.example.com/users/%s/profile'));
+
+// TODO: Instantiate your converter and convert some Markdown
+
+ +

When creating your own, you can provide two parameters to the constructor:

+ +
    +
  • A URL template where %s is replaced with the username (required)
  • +
  • A regular expression to parse and validate the username (optional - defaults to '/^[A-Za-z0-9_]+(?!\w)/')
  • +
+ +

Note that @mention-style linking doesn’t actually require you to add the extension - just the InlineParser of your choice.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/commonmark/index.html b/1.4/extensions/commonmark/index.html new file mode 100644 index 0000000000..0b86bc85f1 --- /dev/null +++ b/1.4/extensions/commonmark/index.html @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically included for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Or if you call the Environment::createCommonMarkEnvironment() helper:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\CommonMarkCoreExtension;
+use League\CommonMark\HtmlRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/disallowed-raw-html/index.html b/1.4/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..62de08f7fc --- /dev/null +++ b/1.4/extensions/disallowed-raw-html/index.html @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically filters certain HTML tags when rendering output, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('I cannot change the page <title>anymore</title>');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/external-links/index.html b/1.4/extensions/external-links/index.html new file mode 100644 index 0000000000..8411a835f8 --- /dev/null +++ b/1.4/extensions/external-links/index.html @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Adds a rel="noopener noreferrer" attribute
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Set your configuration
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\HtmlElement;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+
+    /**
+     * @param Link                     $inline
+     * @param ElementRendererInterface $htmlRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . \get_class($inline));
+        }
+
+        if ($inline->getData('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to add a custom icon by targeting the html_class given in the configuration:

+ +
$config = [
+    'external_link' => [
+        'html_class' => 'external',
+    ],
+];
+
+ +
/**
+ * Custom SVG Icon.
+ */
+a[target="_blank"]::after,
+a.external::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link External (https://iconify.design/icon-sets/octicon/link-external.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 12 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M11 10h1v3c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h3v1H1v10h10v-3zM6 2l2.25 2.25L5 7.5 6.5 9l3.25-3.25L12 8V2H6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/github-flavored-markdown/index.html b/1.4/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..32da09ab82 --- /dev/null +++ b/1.4/extensions/github-flavored-markdown/index.html @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/heading-permalinks/index.html b/1.4/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..32d81b820a --- /dev/null +++ b/1.4/extensions/heading-permalinks/index.html @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Set your configuration
+$config = [
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'user-content',
+        'inner_contents' => HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS,
+        'insert' => 'before',
+        'title' => 'Permalink',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

inner_contents

+ +

This controls the HTML you want to appear inside of the generated <a> tag. Usually this would be something you’d style as some kind of link icon.

+ +

By default, we provide an embedded Octicon link SVG, but you can replace this with any custom HTML you wish.

+ +

insert

+ +

This controls whether the anchor is added to the beginning of the <h1>, <h2> etc. tag or to the end. Can be set to either 'before' or 'after'.

+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'inner_contents' => '¶',
+        'insert' => 'after',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the icon just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing inner_contents:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'inner_contents' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/inlines-only/index.html b/1.4/extensions/inlines-only/index.html new file mode 100644 index 0000000000..5a2a2379fc --- /dev/null +++ b/1.4/extensions/inlines-only/index.html @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+
+// Create a new, empty environment
+$environment = new Environment();
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/overview/index.html b/1.4/extensions/overview/index.html new file mode 100644 index 0000000000..2ea452b9b9 --- /dev/null +++ b/1.4/extensions/overview/index.html @@ -0,0 +1,510 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
External LinksTags external links with additional markup1.3.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/smart-punctuation/index.html b/1.4/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..1a1352c3d9 --- /dev/null +++ b/1.4/extensions/smart-punctuation/index.html @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Set your configuration
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/strikethrough/index.html b/1.4/extensions/strikethrough/index.html new file mode 100644 index 0000000000..c36a080d25 --- /dev/null +++ b/1.4/extensions/strikethrough/index.html @@ -0,0 +1,407 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/table-of-contents/index.html b/1.4/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..f87895d15a --- /dev/null +++ b/1.4/extensions/table-of-contents/index.html @@ -0,0 +1,557 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Set your configuration
+$config = [
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/tables/index.html b/1.4/extensions/tables/index.html new file mode 100644 index 0000000000..c1400daca1 --- /dev/null +++ b/1.4/extensions/tables/index.html @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Table\TableExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/extensions/task-lists/index.html b/1.4/extensions/task-lists/index.html new file mode 100644 index 0000000000..2a314e0db3 --- /dev/null +++ b/1.4/extensions/task-lists/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convertToHtml($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/index.html b/1.4/index.html new file mode 100644 index 0000000000..8ce0fb8902 --- /dev/null +++ b/1.4/index.html @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/installation/index.html b/1.4/installation/index.html new file mode 100644 index 0000000000..ef74240f2c --- /dev/null +++ b/1.4/installation/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +

In your project root just run:

+ +
composer require league/commonmark:^1.4
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^1.4. This is equivalent to >=1.4 <2.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/security/index.html b/1.4/security/index.html new file mode 100644 index 0000000000..ef6cfd6c0d --- /dev/null +++ b/1.4/security/index.html @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convertToHtml($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.4/upgrading/index.html b/1.4/upgrading/index.html new file mode 100644 index 0000000000..77f283aa57 --- /dev/null +++ b/1.4/upgrading/index.html @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.3 - 1.4 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.4. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 1.3 to 1.4

+ +

Changes

+ +

Rendering block/inline subclasses

+ +

Imagine you have the following inline elements:

+ +
class Link extends AbstractWebResource { } // This is the is the standard CommonMark "Link" element
+
+class ShortLink extends Link { } // A custom inline node type you created which extends "Link"
+
+class BitlyLink extends ShortLink { } // Another custom inline node type you created
+
+ +

Prior to 1.4, you’d have to manually register corresponding inline renderers for each one:

+ +
/** @var \League\CommonMark\Environment $environment */
+$environment->addInlineRenderer(Link::class, new LinkRenderer()); // this line is usually automatically done for you
+$environment->addInlineRenderer(ShortLink::class, new LinkRenderer()); // register for custom node type; required before 1.4
+$environment->addInlineRenderer(BitlyLink::class, new LinkRenderer()); // register for custom node type; required before 1.4
+
+ +

But in 1.4 onwards, you no longer need to manually register that LinkRenderer for subclasses (like ShortLink and BitlyLink in the example above) - if the Environment can’t find a registered renderer for that specific block/inline node type, we’ll automatically check if the node’s parent classes have a registered renderer and use that instead.

+ +

Previously, if you forgot to register those renderers, the rendering process would fail with a RuntimeException like “Unable to find corresponding renderer”.

+ +

ListBlock::TYPE_ constant values

+ +

The two constants in the ListBlock class no longer contain title-cased values - the first character is now lowercased. Ideally, you should be referencing the constants, but if you were instead hard-coding these values in your application, you may need to adjust those hard-coded strings.

+ +

Deprecations

+ +

Several things have been deprecated in 1.4 - they’ll continue to work, but consider using alternatives to make your code easier to upgrade to 2.0 when these deprecated things are removed.

+ +

ListBlock::TYPE_UNORDERED constant

+ +

The ListBlock::TYPE_UNORDERED constant has been deprecated, use ListBlock::TYPE_BULLET instead.

+ +

bin/commonmark command

+ +

This command has been buggy to test and is relatively unpopular, so this will be removed in 2.0. If you need this type of functionality, consider writing your own script with a Converter/Environment configured exactly how you want it.

+ +

ArrayCollection methods

+ +

This class has several unused methods, or methods with an existing alternative:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Method NameAlternative
add($value)$collection[] = $value
set($key, $value)$collection[$key] = $value
get($key)$collection[$key]
remove($key)unset($collection[$key])
isEmpty()count($collection) === 0
contains($value)in_array($value, $collection->toArray(), true)
indexOf($value)array_search($value, $collection->toArray(), true)
containsKey($key)isset($collection[$key])
replaceWith()(none provided)
removeGaps()(none provided)
+ +

Converter and ConverterInterface

+ +

The Converter class has been deprecated - switch to using CommonMarkConverter instead.

+ +

The ConverterInterface has been deprecated - switch to using MarkdownConverterInterface instead.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/basic-usage/index.html b/1.5/basic-usage/index.html new file mode 100644 index 0000000000..8b0143d3f8 --- /dev/null +++ b/1.5/basic-usage/index.html @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

Additional customization is also possible, and we have many handy extensions to enable additional syntax and features.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/changelog/index.html b/1.5/changelog/index.html new file mode 100644 index 0000000000..966814c79c --- /dev/null +++ b/1.5/changelog/index.html @@ -0,0 +1,1068 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 1.x releases are shown below. See the full list of releases for the complete changelog.

+ +

1.6.7 - 2022-01-13

+ +

Changed

+ +
    +
  • Added ReturnTypeWillChange attribute to prevent PHP 8.1 deprecation warnings (#785)
  • +
  • Coerced punctuation counts to integers to ensure floats are never used
  • +
+ +

1.6.6 - 2021-07-17

+ +

Fixed

+ +
    +
  • Fixed Mentions inside of links creating nested links against the spec’s rules (#688)
  • +
+ +

1.6.5 - 2021-06-26

+ +

Changed

+ +
    +
  • Simplified checks for thematic breaks
  • +
+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not handling autolinks by adjusting its priority to -50 (#681)
  • +
+ +

1.6.4 - 2021-06-19

+ +

Changed

+ +
    +
  • Optimized attribute parsing to avoid inspecting every space character (30% performance boost)
  • +
+ +

1.6.3 - 2021-06-19

+ +

Fixed

+ +
    +
  • Fixed incorrect parsing of tilde-fenced code blocks with leading spaces (#676)
  • +
+ +

1.6.2 - 2021-05-12

+ +

Fixed

+ +
    +
  • Fixed incorrect error level for deprecation notices
  • +
+ +

1.6.1 - 2021-05-08

+ +

Fixed

+ +
    +
  • Fixed HeadingPermalinkProcessor skipping text contents from certain nodes (#615)
  • +
+ +

1.6.0 - 2021-05-01

+ +

Please see https://commonmark.thephpleague.com/1.6/upgrading/ for important information about this release and the upcoming 2.0.0 version.

+ +

Added

+ +
    +
  • Added forward-compatibility for configuration options which will be changing in 2.0: +
      +
    • commonmark/enable_em (currently enable_em in 1.x)
    • +
    • commonmark/enable_strong (currently enable_strong in 1.x)
    • +
    • commonmark/use_asterisk (currently use_asterisk in 1.x)
    • +
    • commonmark/use_underscore (currently use_underscore in 1.x)
    • +
    • commonmark/unordered_list_markers (currently unordered_list_markers in 1.x)
    • +
    • mentions/*/prefix (currently mentions/*/symbol in 1.x)
    • +
    • mentions/*/pattern (currently mentions/*/regex in 1.x)
    • +
    • max_nesting_level (currently supports int and float values in 1.x; will only support int in 2.0)
    • +
    +
  • +
  • Added new MarkdownConverter class for creating converters with custom environments; this replaces the previously-deprecated Converter class
  • +
  • Added new RegexHelper::matchFirst() method
  • +
  • Added new Configuration::exists() method
  • +
+ +

Changed

+ +
    +
  • The max_nesting_level option now defaults to PHP_INT_MAX instead of INF
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the configuration options shown above
  • +
  • Deprecated the ability to pass a custom Environment into the constructors of CommonMarkConverter and GithubFlavoredMarkdownConverter; use MarkdownConverter instead
  • +
  • Deprecated ConfigurableEnvironmentInterface::setConfig(); use mergeConfig() instead
  • +
  • Deprecated calling ConfigurableEnvironmentInterface::mergeConfig() without any parameters
  • +
  • Deprecated calling Configuration::get() and EnvironmentInterface::getConfig() without any parameters
  • +
  • Deprecated calling Configuration::set() without the second $value parameter
  • +
  • Deprecated RegexHelper::matchAll(); use RegexHelper::matchFirst() instead
  • +
  • Deprecated extending the ArrayCollection class; will be marked final in 2.0
  • +
+ +

Fixed

+ +
    +
  • Fixed missing check for empty arrays being passed into the unordered_list_markers configuration option
  • +
+ +

1.5.8 - 2021-03-28

+ +

Fixed

+ +
    +
  • Fixed Table of Contents not rendering heading inlines properly (#587, #588)
  • +
  • Fixed parsing of tables within list items (#590)
  • +
+ +

1.5.7 - 2020-10-31

+ +

Fixed

+ +
    +
  • Fixed mentions not being parsed when appearing after non-word characters (#582)
  • +
+ +

1.5.6 - 2020-10-17

+ +

Changed

+ +
    +
  • Blocks added outside of the parsing context now have their start/end line numbers defaulted to 0 to avoid type errors (#579)
  • +
+ +

Fixed

+ +
    +
  • Fixed replacement blocks not inheriting the start line number of the container they’re replacing (#579)
  • +
  • Fixed Table of Contents blocks not having correct start/end line numbers (#579)
  • +
+ +

1.5.5 - 2020-09-13

+ +

Changed

+ +
    +
  • Bumped CommonMark spec compliance to 0.28.2
  • +
+ +

Fixed

+ +
    +
  • Fixed textarea elements not being treated as a type 1 HTML block (like script, style, or pre)
  • +
  • Fixed autolink processor not handling other unmatched trailing parentheses
  • +
+ +

1.5.4 - 2020-08-18

+ +

Fixed

+ +
    +
  • Fixed footnote ID configuration not taking effect (#524, #530)
  • +
  • Fixed heading permalink slugs not being unique (#531, #534)
  • +
+ +

1.5.3 - 2020-07-19

+ +

Fixed

+ +
    +
  • Fixed regression of multi-byte inline parser characters not being matched
  • +
+ +

1.5.2 - 2020-07-19

+ +

Changed

+ +
    +
  • Significantly improved performance of the inline parser regex
  • +
+ +

Fixed

+ +
    +
  • Fixed parent class lookups for non-existent classes on PHP 8 (#517)
  • +
+ +

1.5.1 - 2020-06-27

+ +

Fixed

+ +
    +
  • Fixed UTF-8 encoding not being checked in the UrlEncoder utility (#509) or the Cursor
  • +
+ +

1.5.0 - 2020-06-21

+ +

Added

+ +
    +
  • Added new AttributesExtension based on https://github.com/webuni/commonmark-attributes-extension (#474)
  • +
  • Added new FootnoteExtension based on https://github.com/rezozero/commonmark-ext-footnotes (#474)
  • +
  • Added new MentionExtension to replace InlineMentionParser with more flexibility and customization
  • +
  • Added the ability to render TableOfContents nodes anywhere in a document (given by a placeholder)
  • +
  • Added the ability to properly clone Node objects
  • +
  • Added options to customize the value of rel attributes set via the ExternalLink extension (#476)
  • +
  • Added a new heading_permalink/slug_normalizer configuration option to allow custom slug generation (#460)
  • +
  • Added a new heading_permalink/symbol configuration option to replace the now deprecated heading_permalink/inner_contents configuration option (#505)
  • +
  • Added SlugNormalizer and TextNormalizer classes to make normalization reusable by extensions (#485)
  • +
  • Added new classes: +
      +
    • TableOfContentsGenerator
    • +
    • TableOfContentsGeneratorInterface
    • +
    • TableOfContentsPlaceholder
    • +
    • TableOfContentsPlaceholderParser
    • +
    • TableOfContentsPlaceholderRenderer
    • +
    +
  • +
+ +

Changed

+ +
    +
  • “Moved” the TableOfContents class into a new Node sub-namespace (with backward-compatibility)
  • +
  • Reference labels are now generated and stored in lower-case instead of upper-case
  • +
  • Reference labels are no longer normalized inside the Reference, only the ReferenceMap
  • +
+ +

Fixed

+ +
    +
  • Fixed reference label case folding polyfill not being consistent between different PHP versions
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the CommonMarkConverter::VERSION constant (#496)
  • +
  • Deprecated League\CommonMark\Extension\Autolink\InlineMentionParser (use League\CommonMark\Extension\Mention\MentionParser instead)
  • +
  • Deprecated everything under League\CommonMark\Extension\HeadingPermalink\Slug (use the classes under League\CommonMark\Normalizer instead)
  • +
  • Deprecated League\CommonMark\Extension\TableOfContents\TableOfContents (use the one in the new Node sub-namespace instead)
  • +
  • Deprecated the STYLE_ and NORMALIZE_ constants in TableOfContentsBuilder (use the ones in TableOfContentsGenerator instead)
  • +
  • Deprecated the \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant (#505)
  • +
  • Deprecated the heading_permalink/inner_contents configuration option in the HeadingPermalink extension (use the new heading_permalink/symbol configuration option instead) (#505)
  • +
+ +

1.4.3 - 2020-05-04

+ +

Fixed

+ +
    +
  • Fixed certain Unicode letters, numbers, and marks not being preserved when generating URL slugs (#467)
  • +
+ +

1.4.2 - 2020-04-24

+ +

Fixed

+ +
    +
  • Fixed inline code blocks not be included within heading permalinks (#457)
  • +
+ +

1.4.1 - 2020-04-20

+ +

Fixed

+ +
    +
  • Fixed BC break caused by ConverterInterface alias not being used by some DI containers (#454)
  • +
+ +

1.4.0 - 2020-04-18

+ +

Added

+ +
    +
  • Added new Heading Permalink extension (#420)
  • +
  • Added new Table of Contents extension (#441)
  • +
  • Added new MarkdownConverterInterface as a long-term replacement for ConverterInterface (#439)
  • +
  • Added new DocumentPreParsedEvent event (#427, #359, #399)
  • +
  • Added new ListBlock::TYPE_BULLET constant as a replacement for ListBlock::TYPE_UNORDERED
  • +
  • Added new MarkdownInput class and MarkdownInputInterface to handle pre-parsing and allow listeners to replace Markdown contents
  • +
+ +

Changed

+ +
    +
  • Block & inline renderers will now render child classes automatically (#222, #209)
  • +
  • The ListBlock constants now use fully-lowercased values instead of titlecased values
  • +
  • Significantly improved typing
  • +
+ +

Fixed

+ +
    +
  • Fixed loose comparison when checking for table alignment
  • +
  • Fixed StaggeredDelimiterProcessor returning from a void function
  • +
+ +

Deprecated

+ +
    +
  • The Converter class has been deprecated; use CommonMarkConverter instead (#438, #439)
  • +
  • The ConverterInterface has been deprecated; use MarkdownConverterInterface instead (#438, #439)
  • +
  • The bin/commonmark script has been deprecated
  • +
  • The following methods of ArrayCollection have been deprecated: +
      +
    • add()
    • +
    • set()
    • +
    • get()
    • +
    • remove()
    • +
    • isEmpty()
    • +
    • contains()
    • +
    • indexOf()
    • +
    • containsKey()
    • +
    • replaceWith()
    • +
    • removeGaps()
    • +
    +
  • +
  • The ListBlock::TYPE_UNORDERED constant has been deprecated, use ListBlock::TYPE_BULLET instead
  • +
+ +

1.3.4 - 2020-04-13

+ +

Fixed

+ +
    +
  • Fixed configuration/environment not being injected into event listeners when adding them via [$instance, 'method'] callable syntax (#440)
  • +
+ +

1.3.3 - 2020-04-05

+ +

Fixed

+ +
    +
  • Fixed event listeners not having the environment or configuration injected if they implemented the EnvironmentAwareInterface or ConfigurationAwareInterface (#423)
  • +
+ +

1.3.2 - 2020-03-25

+ +

Fixed

+ +
    +
  • Optimized URL normalization in cases where URLs don’t contain special characters (#417, #418)
  • +
+ +

1.3.1 - 2020-02-28

+ +

Fixed

+ +
    +
  • Fixed return types of Environment::createCommonMarkEnvironment() and Environment::createGFMEnvironment()
  • +
+ +

1.3.0 - 2020-02-09

+ +

ℹ️ Do you use league/commonmark-ext* packages? Those features are now included directly in this library! See #409 for details on making the switch.

+ +

Added

+ +
    +
  • Added (optional) full GFM support! 🎉🎉🎉 (#409)
  • +
  • Added check to ensure Markdown input is valid UTF-8 (#401, #405)
  • +
  • Added new unordered_list_markers configuration option (#408, #411)
  • +
+ +

Changed

+ +
    +
  • Introduced several micro-optimizations for a 5-10% performance boost
  • +
+ +

1.2.2 - 2020-01-16

+ +

This release contains the same changes as 1.1.3:

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.1.3 - 2020-01-16

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.2.1 - 2020-01-15

+ +

Changed

+ +
    +
  • Introduced several micro-optimizations, reducing the parse time by 8%
  • +
+ +

1.2.0 - 2020-01-09

+ +

Changed

+ +
    +
  • Removed URL decoding step before encoding (more performant and better matches the JS library)
  • +
  • Removed redundant token from HTML tag regex
  • +
+ +

1.1.2 - 2019-12-10

+ +

Fixed

+ +
    +
  • Fixed URL normalization not handling non-UTF-8 sequences properly (#395, #396)
  • +
+ +

1.1.1 - 2019-11-11

+ +

Fixed

+ +
    +
  • Fixed handling of link destinations with unbalanced unescaped parens
  • +
  • Fixed adding delimiters to stack which can neither open nor close a run
  • +
+ +

1.1.0 - 2019-10-31

+ +

Added

+ +
    +
  • Added a new Html5EntityDecoder class (#387)
  • +
+ +

Changed

+ +
    +
  • Improved performance by 10% (#389)
  • +
  • Made entity decoding less memory-intensive (#386, #387)
  • +
+ +

Fixed

+ +
    +
  • Fixed PHP 7.4 compatibility issues
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the Html5Entities class - use Html5EntityDecoder instead (#387)
  • +
+ +

1.0.0 - 2019-06-29

+ +

First stable release! 🎉

+ +

No code changes have been introduced since 1.0.0-rc1

+ +

1.0.0-rc1 - 2019-06-20

+ +

Added

+ +
    +
  • Extracted a ReferenceMapInterface from the ReferenceMap class
  • +
  • Added optional ReferenceMapInterface parameter to the Document constructor
  • +
+ +

Changed

+ +
    +
  • Replaced all references to ReferenceMap with ReferenceMapInterface
  • +
  • ReferenceMap::addReference() no longer returns $this
  • +
+ +

Fixed

+ +
    +
  • Fixed bug where elements with content of "0" wouldn’t be rendered (#376)
  • +
+ +

1.0.0-beta4 - 2019-06-05

+ +

Added

+ +
    +
  • Added event dispatcher functionality (#359, #372)
  • +
+ +

Removed

+ +
    +
  • Removed DocumentProcessorInterface functionality in favor of event dispatching (#373)
  • +
+ +

1.0.0-beta3 - 2019-05-28

+ +

Changed

+ +
    +
  • Made the Delimiter class final and extracted a new DelimiterInterface +
      +
    • Modified most external usages to use this new interface
    • +
    +
  • +
  • Renamed three Delimiter methods: +
      +
    • getOrigDelims() renamed to getOriginalLength()
    • +
    • getNumDelims() renamed to getLength()
    • +
    • setNumDelims() renamed to setLength()
    • +
    +
  • +
  • Made additional classes final: +
      +
    • DelimiterStack
    • +
    • ReferenceMap
    • +
    • ReferenceParser
    • +
    +
  • +
  • Moved ReferenceParser into the Reference sub-namespace
  • +
+ +

Removed

+ +
    +
  • Removed unused Delimiter methods: +
      +
    • setCanOpen()
    • +
    • setCanClose()
    • +
    • setChar()
    • +
    • setIndex()
    • +
    • setInlineNode()
    • +
    +
  • +
  • Removed fluent interface from Delimiter (setter methods now have no return values)
  • +
+ +

1.0.0-beta2 - 2019-05-27

+ +

This beta release fixes a couple of items that were not addressed in the previous beta.

+ +

Changed

+ +
    +
  • DelimiterProcessorInterface::process() will accept any type of AbstractStringContainer now, not just Text nodes
  • +
  • The Delimiter constructor, getInlineNode(), and setInlineNode() no longer accept generic Node elements - only AbstractStringContainers
  • +
+ +

Removed

+ +
    +
  • Removed all deprecated functionality: +
      +
    • The safe option (use html_input and allow_unsafe_links options instead)
    • +
    • All deprecated RegexHelper constants
    • +
    • DocParser::getEnvironment() (you should obtain it some other way)
    • +
    • AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
    • +
    +
  • +
+ +

1.0.0-beta1 - 2019-05-26

+ +

See the upgrading guide for additional information.

+ +

Added

+ +
    +
  • Added proper support for delimiters, including custom delimiters +
      +
    • addDelimiterProcessor() added to ConfigurableEnvironmentInterface and Environment
    • +
    +
  • +
  • Basic delimiters no longer need custom parsers - they’ll be parsed automatically
  • +
  • Added new methods: +
      +
    • AdjacentTextMerger::mergeTextNodesBetweenExclusive()
    • +
    • CommonMarkConveter::getEnvironment()
    • +
    • Configuration::set()
    • +
    +
  • +
  • Extracted some new interfaces from base classes: +
      +
    • DocParserInterface created from DocParser
    • +
    • ConfigurationInterface created from Configuration
    • +
    • ReferenceInterface created from Reference
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Renamed several methods of the Configuration class: +
      +
    • getConfig() renamed to get()
    • +
    • mergeConfig() renamed to merge()
    • +
    • setConfig() renamed to replace()
    • +
    +
  • +
  • Changed ConfigurationAwareInterface::setConfiguration() to accept the new ConfigurationInterface instead of the concrete class
  • +
  • Renamed the AdjoiningTextCollapser class to AdjacentTextMerger +
      +
    • Replaced its collapseTextNodes() method with the new mergeChildNodes() method
    • +
    +
  • +
  • Made several classes final: +
      +
    • Configuration
    • +
    • DocParser
    • +
    • HtmlRenderer
    • +
    • InlineParserEngine
    • +
    • NodeWalker
    • +
    • Reference
    • +
    • All of the block/inline parsers and renderers
    • +
    +
  • +
  • Reduced visibility of several internal methods to private: +
      +
    • DelimiterStack::findEarliest()
    • +
    • All protected methods in InlineParserEngine
    • +
    +
  • +
  • Marked some classes and methods as @internal
  • +
  • ElementRendererInterface now requires a public renderInline() method; added this to HtmlRenderer
  • +
  • Changed InlineParserEngine::parse() to require an AbstractStringContainerBlock instead of the generic Node class
  • +
  • Un-deprecated the CommonmarkConverter::VERSION constant
  • +
  • The Converter constructor now requires an instance of DocParserInterface instead of the concrete DocParser
  • +
  • Changed Emphasis, Strong, and AbstractWebResource to directly extend AbstractInline instead of the (now-deprecated) intermediary AbstractInlineContainer class
  • +
+ +

Fixed

+ +
    +
  • Fixed null errors when inserting sibling Nodes without parents
  • +
  • Fixed NodeWalkerEvent not requiring a Node via its constructor
  • +
  • Fixed Reference::normalizeReference() improperly converting to uppercase instead of performing proper Unicode case-folding
  • +
  • Fixed strong emphasis delimiters not being preserved when enable_strong is set to false (it now works identically to enable_em)
  • +
+ +

Deprecated

+ +
    +
  • Deprecated DocParser::getEnvironment() (you should obtain it some other way)
  • +
  • Deprecated AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
  • +
+ +

Removed

+ +
    +
  • Removed inline processor functionality now that we have proper delimiter support: +
      +
    • Removed addInlineProcessor() from ConfigurableEnvironmentInterface and Environment
    • +
    • Removed getInlineProcessors() from EnvironmentInterface and Environment
    • +
    • Removed EmphasisProcessor
    • +
    • Removed InlineProcessorInterface
    • +
    +
  • +
  • Removed EmphasisParser now that we have proper delimiter support
  • +
  • Removed support for non-UTF-8-compatible encodings +
      +
    • Removed getEncoding() from ContextInterface
    • +
    • Removed getEncoding(), setEncoding(), and $encoding from Context
    • +
    • Removed getEncoding() and the second $encoding constructor param from Cursor
    • +
    +
  • +
  • Removed now-unused methods +
      +
    • Removed DelimiterStack::getTop() (no replacement)
    • +
    • Removed DelimiterStack::iterateByCharacters() (use the new processDelimiters() method instead)
    • +
    • Removed the protected DelimiterStack::findMatchingOpener() method
    • +
    +
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/command-line/index.html b/1.5/command-line/index.html new file mode 100644 index 0000000000..1c0bc2bfa0 --- /dev/null +++ b/1.5/command-line/index.html @@ -0,0 +1,430 @@ + + + + + + + + + + + + + + + + + Command Line - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Command Line

+ +

This functionality has been deprecated in version 1.4 and will be removed in 2.0.

+ +

Markdown can be converted at the command line using the ./bin/commonmark script.

+ +

Usage

+ +
./bin/commonmark [OPTIONS] [FILE]
+
+ +
    +
  • -h, --help: Shows help and usage information
  • +
  • --enable-em: Disable <em> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --enable-strong: Disable <strong> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --use-asterisk: Disable parsing of * for emphasis by setting to 0; enable with 1 (default: 1)
  • +
  • --use-underscore: Disable parsing of _ for emphasis by setting to 0; enable with 1 (default: 1)
  • +
+ +

If no file is given, input will be read from STDIN.

+ +

Output will be written to STDOUT.

+ +

Examples

+ +

Converting a file named document.md

+ +
./bin/commonmark document.md
+
+ +

Converting a file and saving its output

+ +
./bin/commonmark document.md > output.html
+
+ +

Converting from STDIN

+ +
echo -e '# Hello World!' | ./bin/commonmark
+
+ +

Converting from STDIN and saving the output

+ +
echo -e '# Hello World!' | ./bin/commonmark > output.html
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/configuration/index.html b/1.5/configuration/index.html new file mode 100644 index 0000000000..6c268b9560 --- /dev/null +++ b/1.5/configuration/index.html @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

You can provide an array of configuration options to the CommonMarkConverter when creating it:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter([
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'enable_em' => true,
+    'enable_strong' => true,
+    'use_asterisk' => true,
+    'use_underscore' => true,
+    'unordered_list_markers' => ['-', '*', '+'],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => INF,
+]);
+
+ +

Here’s a list of currently-supported options:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
  • +
  • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
  • +
  • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
  • +
  • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
  • +
  • unordered_list_markers - Array of characters that can be used to indicated a bulleted list (default: ["-", "*", "+"])
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: infinite). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested. Added in 0.17.
  • +
+ +

Additional configuration options are available for some of the available extensions - refer to their individual documentation for more details.

+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

The Environment also exposes three methods for managing the configuration:

+ +
    +
  • setConfig(array $config = []) - Replace the current configuration with something else
  • +
  • mergeConfig(array $config = []) - Recursively merge the current configuration with the given options
  • +
  • getConfig(string $key, $default = null) - Returns the config value. For nested configs, use a /-separate path; for example: renderer/soft_break
  • +
+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/abstract-syntax-tree/index.html b/1.5/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..48acbb5f55 --- /dev/null +++ b/1.5/customization/abstract-syntax-tree/index.html @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the DocParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent (see the Event Dispatcher documentation)
  • +
+ +

Traversal

+ +

The following methods can be used to traverse the AST:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

Iteration / Walking the Tree

+ +

If you’d like to iterate through all the nodes, use the walker() method to obtain an instance of NodeWalker. This will walk through the entire tree, emitting NodeWalkerEvents along the way.

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'I am ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

This walker doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes.

+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/block-parsing/index.html b/1.5/customization/block-parsing/index.html new file mode 100644 index 0000000000..2ee5659365 --- /dev/null +++ b/1.5/customization/block-parsing/index.html @@ -0,0 +1,497 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

Block parsers should implement BlockParserInterface and implement the following method:

+ +

parse()

+ +
public function parse(ContextInterface $context, Cursor $cursor): bool;
+
+ +

When parsing a new line, the DocParser iterates through all registered block parsers and calls their parse() method. Each parser must determine whether it can handle the given line; if so, it should parse the given block and return true.

+ +

Parameters

+ +
    +
  • ContextInterface $context - Provides information about the current context of the DocParser. Includes access to things like the document, current block container, and more.
  • +
  • Cursor $cursor - The Cursor encapsulates the current state of the line being parsed and provides helpers for looking around the current position.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the line will be parsed as text.

+ +

Returning true tells the engine that you’ve successfully parsed the block at the given position. It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of syntax indicating the block start
  2. +
  3. Add the parsed block via $context->addBlock()
  4. +
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible
  • +
  • Your parse() method may be called thousands of times so be sure your code is optimized
  • +
+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

Block elements also play a role during the parsing process as they tell the underlying engine how to handle subsequent blocks that are found.

+ +

AbstractBlockElement Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
canContain(...)Tell the engine whether a subsequent block can be added as a child of yours
isCode()Returns whether this block represents an extra-greedy <code> block
matchesNextLine(...)Returns whether this block continues onto the next line (some blocks are multi-line)
shouldLastLineBeBlank()Returns whether the last line should be blank (primarily used by ListItem elements)
finalize(...)Finalizes the block after all child items have been added, thus marking it as closed for modification
+ +

For examples on how these methods are used, see the core block element classes included with this library.

+ +

AbstractStringContainerBlock

+ +

If your element can contain strings of text, you should extend AbstractStringContainerBlock instead of AbstractBlock. This provides some additional methods needed to manage that inner text:

+ + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
handleRemainingContents(...)This is called when a block has been created but some other text still exists on that line
addLine(...)Adds the given line of text to the block element
getStringContent()Returns the strings contained with that block element
+ +

InlineContainerInterface

+ +

If the text contained by your block should be parsed for inline elements, you should also implement the InlineContainerInterface. This doesn’t add any new methods but does signal to the engine that inline parsing is required.

+ +

Multi-line Code Blocks

+ +

If you have a block which spans multiple lines and doesn’t contain any child blocks, consider having isCode() return true. Code blocks have a special feature which enables “greedy parsing” - once it first parses your block, the engine will assume that most of the subsequent lines of Markdown belong to your block - it won’t try using any other parsers until your parser’s matchesNextLine() method returns false, indicating that we’ve reached the end of that code block.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/block-rendering/index.html b/1.5/customization/block-rendering/index.html new file mode 100644 index 0000000000..f546797f7b --- /dev/null +++ b/1.5/customization/block-rendering/index.html @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + Block Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Block Rendering

+ +

Block renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement BlockRendererInterface and its render() method:

+ +

render()

+ +
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false);
+
+ +

The HtmlRenderer will call this method whenever a supported block element is encountered in the AST being rendered.

+ +

If the method can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractBlock $block - The encountered block you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to render inlines or easily generate HTML tags
  • +
  • $inTightList = false - Whether the element is being rendered in a tight list or not
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the block and any of its contents. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Block Renderers

+ +

When registering your renderer, you must tell the Environment which block element class your renderer should handle. For example:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the block class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addBlockRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple block types:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Block\Element\IndentedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addBlockRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addBlockRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Node\Block\AbstractBlock;
+use League\CommonMark\Renderer\Block\BlockRendererInterface;
+use League\CommonMark\Renderer\ElementRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements BlockRendererInterface
+{
+    public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addBlockRenderer('League\CommonMark\Block\Element\ThematicBreak', new TextDividerRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any inlines your block might contain!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/cursor/index.html b/1.5/customization/cursor/index.html new file mode 100644 index 0000000000..95b4e29be0 --- /dev/null +++ b/1.5/customization/cursor/index.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter()Returns the character at the current position
getCharacter(int $index)Returns the character at the given absolute position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/delimiter-processing/index.html b/1.5/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..ea10dc9bb4 --- /dev/null +++ b/1.5/customization/delimiter-processing/index.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse);
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/document-processing/index.html b/1.5/customization/document-processing/index.html new file mode 100644 index 0000000000..2bc8e62835 --- /dev/null +++ b/1.5/customization/document-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/1.5/customization/environment/index.html b/1.5/customization/environment/index.html new file mode 100644 index 0000000000..fbe3010301 --- /dev/null +++ b/1.5/customization/environment/index.html @@ -0,0 +1,480 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

A pre-configured Environment can be obtained like this:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+ +

All of the core renders, parsers, etc. needed to implement the CommonMark spec will be pre-registered and ready to go.

+ +

You can customize this default Environment (or even a new, empty one) using any of the methods below (from the ConfigurableEnvironmentInterface interface).

+ +

mergeConfig()

+ +
public function mergeConfig(array $config = []);
+
+ +

Merges the given configuration settings into any existing ones.

+ +

setConfig()

+ +
public function setConfig(array $config = []);
+
+ +

Completely replaces the previous configuration settings with the new $config you provide.

+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. This is typically how you’d integrate third-party extensions with this library.

+ +

addBlockParser()

+ +
public function addBlockParser(BlockParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addBlockRenderer()

+ +
public function addBlockRenderer(string $blockClass, BlockRendererInterface $blockRenderer, int $priority = 0);
+
+ +

Registers a BlockRendererInterface to handle a specific type of block ($blockClass) with the given priority (a higher number will be executed earlier).

+ +

See Block Rendering for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addInlineRenderer()

+ +
public function addInlineRenderer(string $inlineClass, InlineRendererInterface $renderer, int $priority = 0);
+
+ +

Registers an InlineRendererInterface to handle a specific type of inline ($inlineClass) with the given priority (a higher number will be executed earlier). +A single renderer can handle multiple inline classes, but you must register it separately for each type. (The same renderer instance can be re-used if desired.)

+ +

See Inline Rendering for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/event-dispatcher/index.html b/1.5/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..971c12eca9 --- /dev/null +++ b/1.5/customization/event-dispatcher/index.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code. If you’re familiar with Symfony’s EventDispatcher or PSR-14 then this should be very familiar to you.

+ +

Event Class

+ +

All events must extend from the AbstractEvent class:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - an instance of AbstractEvent to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Inline\Element\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event)
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data['attributes']['class'] = 'external-link';
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfig('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = Environment::createCommonMarkEnvironment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convertToHtml($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/extensions/index.html b/1.5/customization/extensions/index.html new file mode 100644 index 0000000000..e3878c05e9 --- /dev/null +++ b/1.5/customization/extensions/index.html @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a ConfigurableEnvironmentInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\ConfigurableEnvironmentInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(ConfigurableEnvironmentInterface $environment)
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new EmojiExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/inline-parsing/index.html b/1.5/customization/inline-parsing/index.html new file mode 100644 index 0000000000..659b92214c --- /dev/null +++ b/1.5/customization/inline-parsing/index.html @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getCharacters()

+ +

This method should return an array of single characters which the inline parser engine should stop on. When it does find a match in the current line the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has stopped at a matching character; and,
  2. +
  3. No other inline parsers have successfully parsed the character
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser, including the Cursor used to parse the current line.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line/character for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the character will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return ['@'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+        // Save the cursor state in case we need to rewind and bail
+        $previousState = $cursor->saveState();
+        // Advance past the @ symbol to keep parsing simpler
+        $cursor->advance();
+        // Parse the handle
+        $handle = $cursor->match('/^[A-Za-z0-9_]{1,15}(?!\w)/');
+        if (empty($handle)) {
+            // Regex failed to match; this isn't a valid Twitter handle
+            $cursor->restoreState($previousState);
+            return false;
+        }
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Image;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return [':'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // The next character must be a paren; if not, then bail
+        // We use peek() to quickly check without affecting the cursor
+        $nextChar = $cursor->peek();
+        if ($nextChar !== '(' && $nextChar !== ')') {
+            return false;
+        }
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($nextChar === ')') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($nextChar === '(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible.
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/inline-rendering/index.html b/1.5/customization/inline-rendering/index.html new file mode 100644 index 0000000000..2131ef611c --- /dev/null +++ b/1.5/customization/inline-rendering/index.html @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + Inline Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Inline Rendering

+ +

Inline renderers are responsible for converting the parsed inline elements into their HTML representation.

+ +

All inline renderers should implement InlineRendererInterface and its render() method:

+ +

render()

+ +

Block elements are responsible for calling $htmlRenderer->renderInlines() if they contain inline elements. This in turns causes the HtmlRenderer to call this render() method whenever a supported inline element is encountered.

+ +

If the method can only handle certain inline types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractInline $inline - The encountered inline you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to help escape output or easily generate HTML tags
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the entire inline and any contents. This can be an HtmlElement object (preferred; castable to a string) or a string of raw HTML.

+ +

You are responsible for handling any escaping that may be necessary.

+ +

Return null if your renderer cannot handle the given inline element - the next-highest priority renderer will then be given a chance to render it.

+ +

Designating Inline Renderers

+ +

When registering your render, you must tell the Environment which inline element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the inline class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addInlineRenderer('League\CommonMark\Inline\Element\Link', new MyCustomLinkRenderer());
+
+ +

Example

+ +

Here’s a custom renderer which puts a special class on links to external sites:

+ +
use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+use League\CommonMark\HtmlElement;
+
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+    private $host;
+
+    public function __construct($host)
+    {
+        $this->host = $host;
+    }
+
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline));
+        }
+
+        $attrs = array();
+
+        $attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true);
+
+        if (isset($inline->attributes['title'])) {
+            $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true);
+        }
+
+        if ($this->isExternalUrl($inline->getUrl())) {
+            $attrs['class'] = 'external-link';
+        }
+
+        return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children()));
+    }
+
+    private function isExternalUrl($url)
+    {
+        return parse_url($url, PHP_URL_HOST) !== $this->host;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineRenderer(Link::class, new MyCustomLinkRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Some inlines can contain other inlines - don’t forget to render those too!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/customization/overview/index.html b/1.5/customization/overview/index.html new file mode 100644 index 0000000000..21c25a6a50 --- /dev/null +++ b/1.5/customization/overview/index.html @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders you need
  2. +
  3. Set custom configuration options within the Environment
  4. +
  5. Instantiate a DocParser and HtmlRenderer using that Environment
  6. +
  7. Use the DocParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  8. +
  9. Use the HtmlRenderer to convert the AST Document into HTML
  10. +
+ +

CommonMarkConverter handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->setConfig([
+    'html_input' => 'strip',
+]);
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renders convert the parsed blocks/inlines from the AST representation into HTML. There are two types of renderers:

+ + + +

When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you +to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/attributes/index.html b/1.5/extensions/attributes/index.html new file mode 100644 index 0000000000..0c20b05402 --- /dev/null +++ b/1.5/extensions/attributes/index.html @@ -0,0 +1,442 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+{#id .class}
+## Header
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the extension
+$environment->addExtension(new AttributesExtension());
+
+// Set your configuration if needed
+$config = [
+    // ...
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/autolinks/index.html b/1.5/extensions/autolinks/index.html new file mode 100644 index 0000000000..b49ca68d2f --- /dev/null +++ b/1.5/extensions/autolinks/index.html @@ -0,0 +1,424 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a separate Mention extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/commonmark/index.html b/1.5/extensions/commonmark/index.html new file mode 100644 index 0000000000..d31bf9c63c --- /dev/null +++ b/1.5/extensions/commonmark/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically included for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Or if you call the Environment::createCommonMarkEnvironment() helper:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\CommonMarkCoreExtension;
+use League\CommonMark\HtmlRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/disallowed-raw-html/index.html b/1.5/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..8e19894271 --- /dev/null +++ b/1.5/extensions/disallowed-raw-html/index.html @@ -0,0 +1,437 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically filters certain HTML tags when rendering output, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('I cannot change the page <title>anymore</title>');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/external-links/index.html b/1.5/extensions/external-links/index.html new file mode 100644 index 0000000000..9e935aa17b --- /dev/null +++ b/1.5/extensions/external-links/index.html @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Set your configuration
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +

+use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\HtmlElement;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+
+    /**
+     * @param Link                     $inline
+     * @param ElementRendererInterface $htmlRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . \get_class($inline));
+        }
+
+        if ($inline->getData('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to add a custom icon by targeting the html_class given in the configuration:

+ +
$config = [
+    'external_link' => [
+        'html_class' => 'external',
+    ],
+];
+
+ +
/**
+ * Custom SVG Icon.
+ */
+a[target="_blank"]::after,
+a.external::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link External (https://iconify.design/icon-sets/octicon/link-external.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 12 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M11 10h1v3c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h3v1H1v10h10v-3zM6 2l2.25 2.25L5 7.5 6.5 9l3.25-3.25L12 8V2H6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/footnotes/index.html b/1.5/extensions/footnotes/index.html new file mode 100644 index 0000000000..418660a9a0 --- /dev/null +++ b/1.5/extensions/footnotes/index.html @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1">&#8617;</a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Set your configuration
+$config = [
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/github-flavored-markdown/index.html b/1.5/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..807e8b8570 --- /dev/null +++ b/1.5/extensions/github-flavored-markdown/index.html @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/heading-permalinks/index.html b/1.5/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..f3465d4455 --- /dev/null +++ b/1.5/extensions/heading-permalinks/index.html @@ -0,0 +1,620 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\Normalizer\SlugNormalizer;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Set your configuration
+$config = [
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'user-content',
+        'insert' => 'before',
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+        'slug_normalizer' => new SlugNormalizer(),
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

inner_contents (deprecated since 1.5.0)

+ +

This controls the HTML you want to appear inside of the generated <a> tag. Usually this would be something you would +style as some kind of link icon, but you can replace this with any custom HTML you wish.

+ +

This option was deprecated in 1.5.0 and will be removed in 2.0.0. Use the symbol option instead.

+ +

This option has no default value and if one is provided, a deprecation warning will be triggered and the symbol +config option below will be ignored completely.

+ +

See the Upgrade Guide for more information.

+ +

insert

+ +

This controls whether the anchor is added to the beginning of the <h1>, <h2> etc. tag or to the end. Can be set to either 'before' or 'after'.

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

slug_normalizer

+ +

“Slugs” are the strings used within the href, name, and id attributes to identify a particular permalink. +By default, this extension will generate slugs based on the contents of the heading, just like GitHub-Flavored Markdown does.

+ +

You can change the string that is used as the “slug” by setting the slug_normalizer option to any class that implements TextNormalizerInterface.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'heading_permalink' => [
+        // ... other options here ...
+        'slug_normalizer' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'heading_permalink' => [
+        // ... other options here ...
+        'slug_normalizer' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/inlines-only/index.html b/1.5/extensions/inlines-only/index.html new file mode 100644 index 0000000000..6acef32d2f --- /dev/null +++ b/1.5/extensions/inlines-only/index.html @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+
+// Create a new, empty environment
+$environment = new Environment();
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/mentions/index.html b/1.5/extensions/mentions/index.html new file mode 100644 index 0000000000..9f5ebf40a1 --- /dev/null +++ b/1.5/extensions/mentions/index.html @@ -0,0 +1,633 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which symbol you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting symbol, a regular expression to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Mention\MentionExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go.
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Set your configuration.
+$config = [
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'symbol'    => '@',
+            'regex'     => '/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'symbol'    => '#',
+            'regex'     => '/^\d+/',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same symbol, the last one registered will
+        // always take precedence.
+        'twitter_handle' => [
+            'symbol'    => '@',
+            'regex'     => '/^[A-Za-z0-9_]{1,15}(?!\w)/',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://twitter.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Symbol └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the symbol (in this case, @). You can use any symbol, regex pattern, or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Node\Inline\AbstractInline;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go.
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Set your configuration.
+$config = [
+    'mentions' => [
+        'github_handle' => [
+            'symbol'    => '@',
+            'regex'     => '/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'symbol'    => '#',
+            'regex'     => '/^\d+/',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'symbol'    => '#',
+            'regex'     => '/^\d+/',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Mention\MentionExtension;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Set your configuration.
+$config = [
+    'mentions' => [
+        'user_url_generator' => [
+            'symbol'    => '@',
+            'regex'     => '/^[a-z0-9]+/i',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/overview/index.html b/1.5/extensions/overview/index.html new file mode 100644 index 0000000000..f71e795028 --- /dev/null +++ b/1.5/extensions/overview/index.html @@ -0,0 +1,540 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new CommonMarkConverter([], $environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/smart-punctuation/index.html b/1.5/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..f5ec82cdae --- /dev/null +++ b/1.5/extensions/smart-punctuation/index.html @@ -0,0 +1,437 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Set your configuration
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/strikethrough/index.html b/1.5/extensions/strikethrough/index.html new file mode 100644 index 0000000000..701d677779 --- /dev/null +++ b/1.5/extensions/strikethrough/index.html @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/table-of-contents/index.html b/1.5/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..bfcb36e0fd --- /dev/null +++ b/1.5/extensions/table-of-contents/index.html @@ -0,0 +1,575 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Set your configuration
+$config = [
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+];
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/tables/index.html b/1.5/extensions/tables/index.html new file mode 100644 index 0000000000..6825b7e6a0 --- /dev/null +++ b/1.5/extensions/tables/index.html @@ -0,0 +1,464 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\Table\TableExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter($config, $environment);
+echo $converter->convertToHtml('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/extensions/task-lists/index.html b/1.5/extensions/task-lists/index.html new file mode 100644 index 0000000000..f32c4e60bc --- /dev/null +++ b/1.5/extensions/task-lists/index.html @@ -0,0 +1,428 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new CommonMarkConverter([], $environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convertToHtml($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/index.html b/1.5/index.html new file mode 100644 index 0000000000..aa44595fdd --- /dev/null +++ b/1.5/index.html @@ -0,0 +1,423 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/installation/index.html b/1.5/installation/index.html new file mode 100644 index 0000000000..97c142c88f --- /dev/null +++ b/1.5/installation/index.html @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +

In your project root just run:

+ +
composer require league/commonmark:^1.5
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^1.5. This is equivalent to >=1.5 <2.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/security/index.html b/1.5/security/index.html new file mode 100644 index 0000000000..c968d4c50b --- /dev/null +++ b/1.5/security/index.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convertToHtml($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.5/upgrading/index.html b/1.5/upgrading/index.html new file mode 100644 index 0000000000..b0c8569ec8 --- /dev/null +++ b/1.5/upgrading/index.html @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.4 - 1.5 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.5. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 1.4 to 1.5

+ +

Changes

+ +

Reference labels are no longer auto-normalized within the Reference constructor. Normalization only occurs within the ReferenceMap.

+ +

Deprecations

+ +

Reference::normalizeReference() has been deprecated. Use TextNormalizer::normalize() instead.

+ +

The InlineMentionParser has been deprecated. Use MentionParser instead.

+ + + +

The following two classes have been deprecated in favor of more-generic text normalizers:

+ + + + + + + + + + + + + + + + + + +
Old ClassNew Class
Extension\HeadingPermalink\Slug\DefaultSlugGeneratorNormalizer\SlugNormalizer
Extension\HeadingPermalink\Slug\SlugGeneratorInterfaceNormalizer\TextNormalizerInterface
+ +

The method signatures of these classes are slightly different:

+ +
public function createSlug(string $input): string;
+
+ +

To:

+ +
public function normalize(string $input, $context = null): string;
+
+ +

heading_permalink/inner_contents configuration option

+ +

Prior to 1.5.0, this configuration option’s default value was an embedded Octicon link SVG, +but any custom HTML could be provided.

+ +

If you wish to restore the previous functionality, you may supply inner_contents with the original default value by +using the constant HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS (which is now also deprecated):

+ +
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+
+$config = [
+    'heading_permalink' => [
+        'inner_contents' => HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS,
+    ],
+];
+
+ +

This configuration option will be removed in 2.0.0 in favor of the new heading_permalink/symbol configuration +option. Moving forward, you will need to supply your own custom icon via CSS by removing the default symbol value:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/basic-usage/index.html b/1.6/basic-usage/index.html new file mode 100644 index 0000000000..cd02be8213 --- /dev/null +++ b/1.6/basic-usage/index.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or you can use the generic MarkdownConverter class to customize the environment with whatever extensions you wish to use:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+$environment->addExtension(new InlinesOnlyExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+// <p><strong>Hello World!</strong></p>
+
+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

Additional customization is also possible, and we have many handy extensions to enable additional syntax and features.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/changelog/index.html b/1.6/changelog/index.html new file mode 100644 index 0000000000..42ece70e95 --- /dev/null +++ b/1.6/changelog/index.html @@ -0,0 +1,1072 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 1.x releases are shown below. See the full list of releases for the complete changelog.

+ +

1.6.7 - 2022-01-13

+ +

Changed

+ +
    +
  • Added ReturnTypeWillChange attribute to prevent PHP 8.1 deprecation warnings (#785)
  • +
  • Coerced punctuation counts to integers to ensure floats are never used
  • +
+ +

1.6.6 - 2021-07-17

+ +

Fixed

+ +
    +
  • Fixed Mentions inside of links creating nested links against the spec’s rules (#688)
  • +
+ +

1.6.5 - 2021-06-26

+ +

Changed

+ +
    +
  • Simplified checks for thematic breaks
  • +
+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not handling autolinks by adjusting its priority to -50 (#681)
  • +
+ +

1.6.4 - 2021-06-19

+ +

Changed

+ +
    +
  • Optimized attribute parsing to avoid inspecting every space character (30% performance boost)
  • +
+ +

1.6.3 - 2021-06-19

+ +

Fixed

+ +
    +
  • Fixed incorrect parsing of tilde-fenced code blocks with leading spaces (#676)
  • +
+ +

1.6.2 - 2021-05-12

+ +

Fixed

+ +
    +
  • Fixed incorrect error level for deprecation notices
  • +
+ +

1.6.1 - 2021-05-08

+ +

Fixed

+ +
    +
  • Fixed HeadingPermalinkProcessor skipping text contents from certain nodes (#615)
  • +
+ +

1.6.0 - 2021-05-01

+ +

Please see https://commonmark.thephpleague.com/1.6/upgrading/ for important information about this release and the upcoming 2.0.0 version.

+ +

Added

+ +
    +
  • Added forward-compatibility for configuration options which will be changing in 2.0: +
      +
    • commonmark/enable_em (currently enable_em in 1.x)
    • +
    • commonmark/enable_strong (currently enable_strong in 1.x)
    • +
    • commonmark/use_asterisk (currently use_asterisk in 1.x)
    • +
    • commonmark/use_underscore (currently use_underscore in 1.x)
    • +
    • commonmark/unordered_list_markers (currently unordered_list_markers in 1.x)
    • +
    • mentions/*/prefix (currently mentions/*/symbol in 1.x)
    • +
    • mentions/*/pattern (currently mentions/*/regex in 1.x)
    • +
    • max_nesting_level (currently supports int and float values in 1.x; will only support int in 2.0)
    • +
    +
  • +
  • Added new MarkdownConverter class for creating converters with custom environments; this replaces the previously-deprecated Converter class
  • +
  • Added new RegexHelper::matchFirst() method
  • +
  • Added new Configuration::exists() method
  • +
+ +

Changed

+ +
    +
  • The max_nesting_level option now defaults to PHP_INT_MAX instead of INF
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the configuration options shown above
  • +
  • Deprecated the ability to pass a custom Environment into the constructors of CommonMarkConverter and GithubFlavoredMarkdownConverter; use MarkdownConverter instead
  • +
  • Deprecated ConfigurableEnvironmentInterface::setConfig(); use mergeConfig() instead
  • +
  • Deprecated calling ConfigurableEnvironmentInterface::mergeConfig() without any parameters
  • +
  • Deprecated calling Configuration::get() and EnvironmentInterface::getConfig() without any parameters
  • +
  • Deprecated calling Configuration::set() without the second $value parameter
  • +
  • Deprecated RegexHelper::matchAll(); use RegexHelper::matchFirst() instead
  • +
  • Deprecated extending the ArrayCollection class; will be marked final in 2.0
  • +
+ +

Fixed

+ +
    +
  • Fixed missing check for empty arrays being passed into the unordered_list_markers configuration option
  • +
+ +

1.5.8 - 2021-03-28

+ +

Fixed

+ +
    +
  • Fixed Table of Contents not rendering heading inlines properly (#587, #588)
  • +
  • Fixed parsing of tables within list items (#590)
  • +
+ +

1.5.7 - 2020-10-31

+ +

Fixed

+ +
    +
  • Fixed mentions not being parsed when appearing after non-word characters (#582)
  • +
+ +

1.5.6 - 2020-10-17

+ +

Changed

+ +
    +
  • Blocks added outside of the parsing context now have their start/end line numbers defaulted to 0 to avoid type errors (#579)
  • +
+ +

Fixed

+ +
    +
  • Fixed replacement blocks not inheriting the start line number of the container they’re replacing (#579)
  • +
  • Fixed Table of Contents blocks not having correct start/end line numbers (#579)
  • +
+ +

1.5.5 - 2020-09-13

+ +

Changed

+ +
    +
  • Bumped CommonMark spec compliance to 0.28.2
  • +
+ +

Fixed

+ +
    +
  • Fixed textarea elements not being treated as a type 1 HTML block (like script, style, or pre)
  • +
  • Fixed autolink processor not handling other unmatched trailing parentheses
  • +
+ +

1.5.4 - 2020-08-18

+ +

Fixed

+ +
    +
  • Fixed footnote ID configuration not taking effect (#524, #530)
  • +
  • Fixed heading permalink slugs not being unique (#531, #534)
  • +
+ +

1.5.3 - 2020-07-19

+ +

Fixed

+ +
    +
  • Fixed regression of multi-byte inline parser characters not being matched
  • +
+ +

1.5.2 - 2020-07-19

+ +

Changed

+ +
    +
  • Significantly improved performance of the inline parser regex
  • +
+ +

Fixed

+ +
    +
  • Fixed parent class lookups for non-existent classes on PHP 8 (#517)
  • +
+ +

1.5.1 - 2020-06-27

+ +

Fixed

+ +
    +
  • Fixed UTF-8 encoding not being checked in the UrlEncoder utility (#509) or the Cursor
  • +
+ +

1.5.0 - 2020-06-21

+ +

Added

+ +
    +
  • Added new AttributesExtension based on https://github.com/webuni/commonmark-attributes-extension (#474)
  • +
  • Added new FootnoteExtension based on https://github.com/rezozero/commonmark-ext-footnotes (#474)
  • +
  • Added new MentionExtension to replace InlineMentionParser with more flexibility and customization
  • +
  • Added the ability to render TableOfContents nodes anywhere in a document (given by a placeholder)
  • +
  • Added the ability to properly clone Node objects
  • +
  • Added options to customize the value of rel attributes set via the ExternalLink extension (#476)
  • +
  • Added a new heading_permalink/slug_normalizer configuration option to allow custom slug generation (#460)
  • +
  • Added a new heading_permalink/symbol configuration option to replace the now deprecated heading_permalink/inner_contents configuration option (#505)
  • +
  • Added SlugNormalizer and TextNormalizer classes to make normalization reusable by extensions (#485)
  • +
  • Added new classes: +
      +
    • TableOfContentsGenerator
    • +
    • TableOfContentsGeneratorInterface
    • +
    • TableOfContentsPlaceholder
    • +
    • TableOfContentsPlaceholderParser
    • +
    • TableOfContentsPlaceholderRenderer
    • +
    +
  • +
+ +

Changed

+ +
    +
  • “Moved” the TableOfContents class into a new Node sub-namespace (with backward-compatibility)
  • +
  • Reference labels are now generated and stored in lower-case instead of upper-case
  • +
  • Reference labels are no longer normalized inside the Reference, only the ReferenceMap
  • +
+ +

Fixed

+ +
    +
  • Fixed reference label case folding polyfill not being consistent between different PHP versions
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the CommonMarkConverter::VERSION constant (#496)
  • +
  • Deprecated League\CommonMark\Extension\Autolink\InlineMentionParser (use League\CommonMark\Extension\Mention\MentionParser instead)
  • +
  • Deprecated everything under League\CommonMark\Extension\HeadingPermalink\Slug (use the classes under League\CommonMark\Normalizer instead)
  • +
  • Deprecated League\CommonMark\Extension\TableOfContents\TableOfContents (use the one in the new Node sub-namespace instead)
  • +
  • Deprecated the STYLE_ and NORMALIZE_ constants in TableOfContentsBuilder (use the ones in TableOfContentsGenerator instead)
  • +
  • Deprecated the \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant (#505)
  • +
  • Deprecated the heading_permalink/inner_contents configuration option in the HeadingPermalink extension (use the new heading_permalink/symbol configuration option instead) (#505)
  • +
+ +

1.4.3 - 2020-05-04

+ +

Fixed

+ +
    +
  • Fixed certain Unicode letters, numbers, and marks not being preserved when generating URL slugs (#467)
  • +
+ +

1.4.2 - 2020-04-24

+ +

Fixed

+ +
    +
  • Fixed inline code blocks not be included within heading permalinks (#457)
  • +
+ +

1.4.1 - 2020-04-20

+ +

Fixed

+ +
    +
  • Fixed BC break caused by ConverterInterface alias not being used by some DI containers (#454)
  • +
+ +

1.4.0 - 2020-04-18

+ +

Added

+ +
    +
  • Added new Heading Permalink extension (#420)
  • +
  • Added new Table of Contents extension (#441)
  • +
  • Added new MarkdownConverterInterface as a long-term replacement for ConverterInterface (#439)
  • +
  • Added new DocumentPreParsedEvent event (#427, #359, #399)
  • +
  • Added new ListBlock::TYPE_BULLET constant as a replacement for ListBlock::TYPE_UNORDERED
  • +
  • Added new MarkdownInput class and MarkdownInputInterface to handle pre-parsing and allow listeners to replace Markdown contents
  • +
+ +

Changed

+ +
    +
  • Block & inline renderers will now render child classes automatically (#222, #209)
  • +
  • The ListBlock constants now use fully-lowercased values instead of titlecased values
  • +
  • Significantly improved typing
  • +
+ +

Fixed

+ +
    +
  • Fixed loose comparison when checking for table alignment
  • +
  • Fixed StaggeredDelimiterProcessor returning from a void function
  • +
+ +

Deprecated

+ +
    +
  • The Converter class has been deprecated; use CommonMarkConverter instead (#438, #439)
  • +
  • The ConverterInterface has been deprecated; use MarkdownConverterInterface instead (#438, #439)
  • +
  • The bin/commonmark script has been deprecated
  • +
  • The following methods of ArrayCollection have been deprecated: +
      +
    • add()
    • +
    • set()
    • +
    • get()
    • +
    • remove()
    • +
    • isEmpty()
    • +
    • contains()
    • +
    • indexOf()
    • +
    • containsKey()
    • +
    • replaceWith()
    • +
    • removeGaps()
    • +
    +
  • +
  • The ListBlock::TYPE_UNORDERED constant has been deprecated, use ListBlock::TYPE_BULLET instead
  • +
+ +

1.3.4 - 2020-04-13

+ +

Fixed

+ +
    +
  • Fixed configuration/environment not being injected into event listeners when adding them via [$instance, 'method'] callable syntax (#440)
  • +
+ +

1.3.3 - 2020-04-05

+ +

Fixed

+ +
    +
  • Fixed event listeners not having the environment or configuration injected if they implemented the EnvironmentAwareInterface or ConfigurationAwareInterface (#423)
  • +
+ +

1.3.2 - 2020-03-25

+ +

Fixed

+ +
    +
  • Optimized URL normalization in cases where URLs don’t contain special characters (#417, #418)
  • +
+ +

1.3.1 - 2020-02-28

+ +

Fixed

+ +
    +
  • Fixed return types of Environment::createCommonMarkEnvironment() and Environment::createGFMEnvironment()
  • +
+ +

1.3.0 - 2020-02-09

+ +

ℹ️ Do you use league/commonmark-ext* packages? Those features are now included directly in this library! See #409 for details on making the switch.

+ +

Added

+ +
    +
  • Added (optional) full GFM support! 🎉🎉🎉 (#409)
  • +
  • Added check to ensure Markdown input is valid UTF-8 (#401, #405)
  • +
  • Added new unordered_list_markers configuration option (#408, #411)
  • +
+ +

Changed

+ +
    +
  • Introduced several micro-optimizations for a 5-10% performance boost
  • +
+ +

1.2.2 - 2020-01-16

+ +

This release contains the same changes as 1.1.3:

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.1.3 - 2020-01-16

+ +

Fixed

+ +
    +
  • Fixed link parsing edge case (#403)
  • +
+ +

1.2.1 - 2020-01-15

+ +

Changed

+ +
    +
  • Introduced several micro-optimizations, reducing the parse time by 8%
  • +
+ +

1.2.0 - 2020-01-09

+ +

Changed

+ +
    +
  • Removed URL decoding step before encoding (more performant and better matches the JS library)
  • +
  • Removed redundant token from HTML tag regex
  • +
+ +

1.1.2 - 2019-12-10

+ +

Fixed

+ +
    +
  • Fixed URL normalization not handling non-UTF-8 sequences properly (#395, #396)
  • +
+ +

1.1.1 - 2019-11-11

+ +

Fixed

+ +
    +
  • Fixed handling of link destinations with unbalanced unescaped parens
  • +
  • Fixed adding delimiters to stack which can neither open nor close a run
  • +
+ +

1.1.0 - 2019-10-31

+ +

Added

+ +
    +
  • Added a new Html5EntityDecoder class (#387)
  • +
+ +

Changed

+ +
    +
  • Improved performance by 10% (#389)
  • +
  • Made entity decoding less memory-intensive (#386, #387)
  • +
+ +

Fixed

+ +
    +
  • Fixed PHP 7.4 compatibility issues
  • +
+ +

Deprecated

+ +
    +
  • Deprecated the Html5Entities class - use Html5EntityDecoder instead (#387)
  • +
+ +

1.0.0 - 2019-06-29

+ +

First stable release! 🎉

+ +

No code changes have been introduced since 1.0.0-rc1

+ +

1.0.0-rc1 - 2019-06-20

+ +

Added

+ +
    +
  • Extracted a ReferenceMapInterface from the ReferenceMap class
  • +
  • Added optional ReferenceMapInterface parameter to the Document constructor
  • +
+ +

Changed

+ +
    +
  • Replaced all references to ReferenceMap with ReferenceMapInterface
  • +
  • ReferenceMap::addReference() no longer returns $this
  • +
+ +

Fixed

+ +
    +
  • Fixed bug where elements with content of "0" wouldn’t be rendered (#376)
  • +
+ +

1.0.0-beta4 - 2019-06-05

+ +

Added

+ +
    +
  • Added event dispatcher functionality (#359, #372)
  • +
+ +

Removed

+ +
    +
  • Removed DocumentProcessorInterface functionality in favor of event dispatching (#373)
  • +
+ +

1.0.0-beta3 - 2019-05-28

+ +

Changed

+ +
    +
  • Made the Delimiter class final and extracted a new DelimiterInterface +
      +
    • Modified most external usages to use this new interface
    • +
    +
  • +
  • Renamed three Delimiter methods: +
      +
    • getOrigDelims() renamed to getOriginalLength()
    • +
    • getNumDelims() renamed to getLength()
    • +
    • setNumDelims() renamed to setLength()
    • +
    +
  • +
  • Made additional classes final: +
      +
    • DelimiterStack
    • +
    • ReferenceMap
    • +
    • ReferenceParser
    • +
    +
  • +
  • Moved ReferenceParser into the Reference sub-namespace
  • +
+ +

Removed

+ +
    +
  • Removed unused Delimiter methods: +
      +
    • setCanOpen()
    • +
    • setCanClose()
    • +
    • setChar()
    • +
    • setIndex()
    • +
    • setInlineNode()
    • +
    +
  • +
  • Removed fluent interface from Delimiter (setter methods now have no return values)
  • +
+ +

1.0.0-beta2 - 2019-05-27

+ +

This beta release fixes a couple of items that were not addressed in the previous beta.

+ +

Changed

+ +
    +
  • DelimiterProcessorInterface::process() will accept any type of AbstractStringContainer now, not just Text nodes
  • +
  • The Delimiter constructor, getInlineNode(), and setInlineNode() no longer accept generic Node elements - only AbstractStringContainers
  • +
+ +

Removed

+ +
    +
  • Removed all deprecated functionality: +
      +
    • The safe option (use html_input and allow_unsafe_links options instead)
    • +
    • All deprecated RegexHelper constants
    • +
    • DocParser::getEnvironment() (you should obtain it some other way)
    • +
    • AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
    • +
    +
  • +
+ +

1.0.0-beta1 - 2019-05-26

+ +

See the upgrading guide for additional information.

+ +

Added

+ +
    +
  • Added proper support for delimiters, including custom delimiters +
      +
    • addDelimiterProcessor() added to ConfigurableEnvironmentInterface and Environment
    • +
    +
  • +
  • Basic delimiters no longer need custom parsers - they’ll be parsed automatically
  • +
  • Added new methods: +
      +
    • AdjacentTextMerger::mergeTextNodesBetweenExclusive()
    • +
    • CommonMarkConveter::getEnvironment()
    • +
    • Configuration::set()
    • +
    +
  • +
  • Extracted some new interfaces from base classes: +
      +
    • DocParserInterface created from DocParser
    • +
    • ConfigurationInterface created from Configuration
    • +
    • ReferenceInterface created from Reference
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Renamed several methods of the Configuration class: +
      +
    • getConfig() renamed to get()
    • +
    • mergeConfig() renamed to merge()
    • +
    • setConfig() renamed to replace()
    • +
    +
  • +
  • Changed ConfigurationAwareInterface::setConfiguration() to accept the new ConfigurationInterface instead of the concrete class
  • +
  • Renamed the AdjoiningTextCollapser class to AdjacentTextMerger +
      +
    • Replaced its collapseTextNodes() method with the new mergeChildNodes() method
    • +
    +
  • +
  • Made several classes final: +
      +
    • Configuration
    • +
    • DocParser
    • +
    • HtmlRenderer
    • +
    • InlineParserEngine
    • +
    • NodeWalker
    • +
    • Reference
    • +
    • All of the block/inline parsers and renderers
    • +
    +
  • +
  • Reduced visibility of several internal methods to private: +
      +
    • DelimiterStack::findEarliest()
    • +
    • All protected methods in InlineParserEngine
    • +
    +
  • +
  • Marked some classes and methods as @internal
  • +
  • ElementRendererInterface now requires a public renderInline() method; added this to HtmlRenderer
  • +
  • Changed InlineParserEngine::parse() to require an AbstractStringContainerBlock instead of the generic Node class
  • +
  • Un-deprecated the CommonmarkConverter::VERSION constant
  • +
  • The Converter constructor now requires an instance of DocParserInterface instead of the concrete DocParser
  • +
  • Changed Emphasis, Strong, and AbstractWebResource to directly extend AbstractInline instead of the (now-deprecated) intermediary AbstractInlineContainer class
  • +
+ +

Fixed

+ +
    +
  • Fixed null errors when inserting sibling Nodes without parents
  • +
  • Fixed NodeWalkerEvent not requiring a Node via its constructor
  • +
  • Fixed Reference::normalizeReference() improperly converting to uppercase instead of performing proper Unicode case-folding
  • +
  • Fixed strong emphasis delimiters not being preserved when enable_strong is set to false (it now works identically to enable_em)
  • +
+ +

Deprecated

+ +
    +
  • Deprecated DocParser::getEnvironment() (you should obtain it some other way)
  • +
  • Deprecated AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
  • +
+ +

Removed

+ +
    +
  • Removed inline processor functionality now that we have proper delimiter support: +
      +
    • Removed addInlineProcessor() from ConfigurableEnvironmentInterface and Environment
    • +
    • Removed getInlineProcessors() from EnvironmentInterface and Environment
    • +
    • Removed EmphasisProcessor
    • +
    • Removed InlineProcessorInterface
    • +
    +
  • +
  • Removed EmphasisParser now that we have proper delimiter support
  • +
  • Removed support for non-UTF-8-compatible encodings +
      +
    • Removed getEncoding() from ContextInterface
    • +
    • Removed getEncoding(), setEncoding(), and $encoding from Context
    • +
    • Removed getEncoding() and the second $encoding constructor param from Cursor
    • +
    +
  • +
  • Removed now-unused methods +
      +
    • Removed DelimiterStack::getTop() (no replacement)
    • +
    • Removed DelimiterStack::iterateByCharacters() (use the new processDelimiters() method instead)
    • +
    • Removed the protected DelimiterStack::findMatchingOpener() method
    • +
    +
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/command-line/index.html b/1.6/command-line/index.html new file mode 100644 index 0000000000..f6fee60710 --- /dev/null +++ b/1.6/command-line/index.html @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + Command Line - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Command Line

+ +

This functionality has been deprecated in version 1.4 and will be removed in 2.0.

+ +

Markdown can be converted at the command line using the ./bin/commonmark script.

+ +

Usage

+ +
./bin/commonmark [OPTIONS] [FILE]
+
+ +
    +
  • -h, --help: Shows help and usage information
  • +
  • --enable-em: Disable <em> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --enable-strong: Disable <strong> parsing by setting to 0; enable with 1 (default: 1)
  • +
  • --use-asterisk: Disable parsing of * for emphasis by setting to 0; enable with 1 (default: 1)
  • +
  • --use-underscore: Disable parsing of _ for emphasis by setting to 0; enable with 1 (default: 1)
  • +
+ +

If no file is given, input will be read from STDIN.

+ +

Output will be written to STDOUT.

+ +

Examples

+ +

Converting a file named document.md

+ +
./bin/commonmark document.md
+
+ +

Converting a file and saving its output

+ +
./bin/commonmark document.md > output.html
+
+ +

Converting from STDIN

+ +
echo -e '# Hello World!' | ./bin/commonmark
+
+ +

Converting from STDIN and saving the output

+ +
echo -e '# Hello World!' | ./bin/commonmark > output.html
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/configuration/index.html b/1.6/configuration/index.html new file mode 100644 index 0000000000..1d31b9d10f --- /dev/null +++ b/1.6/configuration/index.html @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

Many aspects of this library’s behavior can be tweaked using configuration options.

+ +

You can provide an array of configuration options to the CommonMarkConverter when creating it:

+ +
$config = [
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'commonmark' => [
+        'enable_em' => true,
+        'enable_strong' => true,
+        'use_asterisk' => true,
+        'use_underscore' => true,
+        'unordered_list_markers' => ['-', '*', '+'],
+    ],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => PHP_INT_MAX,
+];
+
+ +

If you’re using the basic CommonMarkConverter or GithubFlavoredMarkdown classes, simply pass the configuration array into the constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s mergeConfig() method instead:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Here's where we set the configuration array:
+$environment->mergeConfig($config);
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

Here’s a list of currently-supported options:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • commonmark - Array of options for configuring CommonMark core functionality: +
      +
    • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
    • +
    • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
    • +
    • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
    • +
    • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
    • +
    • unordered_list_markers - Array of characters that can be used to indicate a bulleted list (default: ["-", "*", "+"])
    • +
    +
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: PHP_INT_MAX). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested. Added in 0.17.
  • +
+ +

Additional configuration options are available for some of the available extensions - refer to their individual documentation for more details.

+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

The Environment also exposes two methods for managing the configuration:

+ +
    +
  • mergeConfig(array $config) - Recursively merge the current configuration with the given options
  • +
  • getConfig(string $key, $default = null) - Returns the config value. For nested configs, use a /-separate path; for example: renderer/soft_break
  • +
+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/abstract-syntax-tree/index.html b/1.6/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..c06737812f --- /dev/null +++ b/1.6/customization/abstract-syntax-tree/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the DocParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent (see the Event Dispatcher documentation)
  • +
+ +

Traversal

+ +

The following methods can be used to traverse the AST:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

Iteration / Walking the Tree

+ +

If you’d like to iterate through all the nodes, use the walker() method to obtain an instance of NodeWalker. This will walk through the entire tree, emitting NodeWalkerEvents along the way.

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'I am ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

This walker doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes.

+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/block-parsing/index.html b/1.6/customization/block-parsing/index.html new file mode 100644 index 0000000000..09784d4da8 --- /dev/null +++ b/1.6/customization/block-parsing/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

Block parsers should implement BlockParserInterface and implement the following method:

+ +

parse()

+ +
public function parse(ContextInterface $context, Cursor $cursor): bool;
+
+ +

When parsing a new line, the DocParser iterates through all registered block parsers and calls their parse() method. Each parser must determine whether it can handle the given line; if so, it should parse the given block and return true.

+ +

Parameters

+ +
    +
  • ContextInterface $context - Provides information about the current context of the DocParser. Includes access to things like the document, current block container, and more.
  • +
  • Cursor $cursor - The Cursor encapsulates the current state of the line being parsed and provides helpers for looking around the current position.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the line will be parsed as text.

+ +

Returning true tells the engine that you’ve successfully parsed the block at the given position. It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of syntax indicating the block start
  2. +
  3. Add the parsed block via $context->addBlock()
  4. +
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible
  • +
  • Your parse() method may be called thousands of times so be sure your code is optimized
  • +
+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

Block elements also play a role during the parsing process as they tell the underlying engine how to handle subsequent blocks that are found.

+ +

AbstractBlockElement Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
canContain(...)Tell the engine whether a subsequent block can be added as a child of yours
isCode()Returns whether this block represents an extra-greedy <code> block
matchesNextLine(...)Returns whether this block continues onto the next line (some blocks are multi-line)
shouldLastLineBeBlank()Returns whether the last line should be blank (primarily used by ListItem elements)
finalize(...)Finalizes the block after all child items have been added, thus marking it as closed for modification
+ +

For examples on how these methods are used, see the core block element classes included with this library.

+ +

AbstractStringContainerBlock

+ +

If your element can contain strings of text, you should extend AbstractStringContainerBlock instead of AbstractBlock. This provides some additional methods needed to manage that inner text:

+ + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
handleRemainingContents(...)This is called when a block has been created but some other text still exists on that line
addLine(...)Adds the given line of text to the block element
getStringContent()Returns the strings contained with that block element
+ +

InlineContainerInterface

+ +

If the text contained by your block should be parsed for inline elements, you should also implement the InlineContainerInterface. This doesn’t add any new methods but does signal to the engine that inline parsing is required.

+ +

Multi-line Code Blocks

+ +

If you have a block which spans multiple lines and doesn’t contain any child blocks, consider having isCode() return true. Code blocks have a special feature which enables “greedy parsing” - once it first parses your block, the engine will assume that most of the subsequent lines of Markdown belong to your block - it won’t try using any other parsers until your parser’s matchesNextLine() method returns false, indicating that we’ve reached the end of that code block.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/block-rendering/index.html b/1.6/customization/block-rendering/index.html new file mode 100644 index 0000000000..bf9c3770b8 --- /dev/null +++ b/1.6/customization/block-rendering/index.html @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + Block Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Block Rendering

+ +

Block renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement BlockRendererInterface and its render() method:

+ +

render()

+ +
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false);
+
+ +

The HtmlRenderer will call this method whenever a supported block element is encountered in the AST being rendered.

+ +

If the method can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractBlock $block - The encountered block you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to render inlines or easily generate HTML tags
  • +
  • $inTightList = false - Whether the element is being rendered in a tight list or not
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the block and any of its contents. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Block Renderers

+ +

When registering your renderer, you must tell the Environment which block element class your renderer should handle. For example:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the block class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addBlockRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple block types:

+ +
use League\CommonMark\Block\Element\FencedCode;
+use League\CommonMark\Block\Element\IndentedCode;
+use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addBlockRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addBlockRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Node\Block\AbstractBlock;
+use League\CommonMark\Renderer\Block\BlockRendererInterface;
+use League\CommonMark\Renderer\ElementRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements BlockRendererInterface
+{
+    public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addBlockRenderer('League\CommonMark\Block\Element\ThematicBreak', new TextDividerRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any inlines your block might contain!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/cursor/index.html b/1.6/customization/cursor/index.html new file mode 100644 index 0000000000..f667818bb6 --- /dev/null +++ b/1.6/customization/cursor/index.html @@ -0,0 +1,523 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter()Returns the character at the current position
getCharacter(int $index)Returns the character at the given absolute position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/delimiter-processing/index.html b/1.6/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..002a4e5b53 --- /dev/null +++ b/1.6/customization/delimiter-processing/index.html @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse);
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/environment/index.html b/1.6/customization/environment/index.html new file mode 100644 index 0000000000..88f74a1b91 --- /dev/null +++ b/1.6/customization/environment/index.html @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

A pre-configured Environment can be obtained like this:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+ +

All of the core renders, parsers, etc. needed to implement the CommonMark spec will be pre-registered and ready to go.

+ +

You can customize this default Environment (or even a new, empty one) using any of the methods below (from the ConfigurableEnvironmentInterface interface).

+ +

mergeConfig()

+ +
public function mergeConfig(array $config = []);
+
+ +

Merges the given configuration settings into any existing ones.

+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. This is typically how you’d integrate third-party extensions with this library.

+ +

addBlockParser()

+ +
public function addBlockParser(BlockParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addBlockRenderer()

+ +
public function addBlockRenderer(string $blockClass, BlockRendererInterface $blockRenderer, int $priority = 0);
+
+ +

Registers a BlockRendererInterface to handle a specific type of block ($blockClass) with the given priority (a higher number will be executed earlier).

+ +

See Block Rendering for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addInlineRenderer()

+ +
public function addInlineRenderer(string $inlineClass, InlineRendererInterface $renderer, int $priority = 0);
+
+ +

Registers an InlineRendererInterface to handle a specific type of inline ($inlineClass) with the given priority (a higher number will be executed earlier). +A single renderer can handle multiple inline classes, but you must register it separately for each type. (The same renderer instance can be re-used if desired.)

+ +

See Inline Rendering for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ +

Accessing the Environment and Configuration within parsers/renderers/etc

+ +

If your custom parser/renderer/listener/etc. implements either EnvironmentAwareInterface or ConfigurationAwareInterface we’ll automatically inject the environment or configuration into them once the environment has been fully initialized. This will provide your code with access to the finalized information it may need.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/event-dispatcher/index.html b/1.6/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..4ff5728d42 --- /dev/null +++ b/1.6/customization/event-dispatcher/index.html @@ -0,0 +1,535 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code. If you’re familiar with Symfony’s EventDispatcher or PSR-14 then this should be very familiar to you.

+ +

Event Class

+ +

All events must extend from the AbstractEvent class:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - an instance of AbstractEvent to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Inline\Element\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event)
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data['attributes']['class'] = 'external-link';
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfig('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = Environment::createCommonMarkEnvironment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convertToHtml($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/extensions/index.html b/1.6/customization/extensions/index.html new file mode 100644 index 0000000000..5146689ad8 --- /dev/null +++ b/1.6/customization/extensions/index.html @@ -0,0 +1,424 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a ConfigurableEnvironmentInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\ConfigurableEnvironmentInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(ConfigurableEnvironmentInterface $environment)
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\MarkdownConverter;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new EmojiExtension());
+$environment->mergeConfig([]);
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/inline-parsing/index.html b/1.6/customization/inline-parsing/index.html new file mode 100644 index 0000000000..b4dd9f2a9d --- /dev/null +++ b/1.6/customization/inline-parsing/index.html @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getCharacters()

+ +

This method should return an array of single characters which the inline parser engine should stop on. When it does find a match in the current line the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has stopped at a matching character; and,
  2. +
  3. No other inline parsers have successfully parsed the character
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser, including the Cursor used to parse the current line.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the current line/character for any reason. (The Cursor state should be restored before returning false if modified). Other parsers will then have a chance to try parsing the line. If all registered parsers return false, the character will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return ['@'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+        // Save the cursor state in case we need to rewind and bail
+        $previousState = $cursor->saveState();
+        // Advance past the @ symbol to keep parsing simpler
+        $cursor->advance();
+        // Parse the handle
+        $handle = $cursor->match('/^[A-Za-z0-9_]{1,15}(?!\w)/');
+        if (empty($handle)) {
+            // Regex failed to match; this isn't a valid Twitter handle
+            $cursor->restoreState($previousState);
+            return false;
+        }
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Image;
+use League\CommonMark\Inline\Parser\InlineParserInterface;
+use League\CommonMark\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getCharacters(): array
+    {
+        return [':'];
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // The next character must be a paren; if not, then bail
+        // We use peek() to quickly check without affecting the cursor
+        $nextChar = $cursor->peek();
+        if ($nextChar !== '(' && $nextChar !== ')') {
+            return false;
+        }
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($nextChar === ')') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($nextChar === '(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance, return false as soon as possible.
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/inline-rendering/index.html b/1.6/customization/inline-rendering/index.html new file mode 100644 index 0000000000..f20d9a00e4 --- /dev/null +++ b/1.6/customization/inline-rendering/index.html @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + Inline Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Inline Rendering

+ +

Inline renderers are responsible for converting the parsed inline elements into their HTML representation.

+ +

All inline renderers should implement InlineRendererInterface and its render() method:

+ +

render()

+ +

Block elements are responsible for calling $htmlRenderer->renderInlines() if they contain inline elements. This in turns causes the HtmlRenderer to call this render() method whenever a supported inline element is encountered.

+ +

If the method can only handle certain inline types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • AbstractInline $inline - The encountered inline you must render
  • +
  • ElementRendererInterface $htmlRenderer - The AST renderer; use this to help escape output or easily generate HTML tags
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the entire inline and any contents. This can be an HtmlElement object (preferred; castable to a string) or a string of raw HTML.

+ +

You are responsible for handling any escaping that may be necessary.

+ +

Return null if your renderer cannot handle the given inline element - the next-highest priority renderer will then be given a chance to render it.

+ +

Designating Inline Renderers

+ +

When registering your render, you must tell the Environment which inline element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+// First param - the inline class type that should use our renderer
+// Second param - instance of the block renderer
+$environment->addInlineRenderer('League\CommonMark\Inline\Element\Link', new MyCustomLinkRenderer());
+
+ +

Example

+ +

Here’s a custom renderer which puts a special class on links to external sites:

+ +
use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\Environment;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+use League\CommonMark\HtmlElement;
+
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+    private $host;
+
+    public function __construct($host)
+    {
+        $this->host = $host;
+    }
+
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . get_class($inline));
+        }
+
+        $attrs = array();
+
+        $attrs['href'] = $htmlRenderer->escape($inline->getUrl(), true);
+
+        if (isset($inline->attributes['title'])) {
+            $attrs['title'] = $htmlRenderer->escape($inline->data['title'], true);
+        }
+
+        if ($this->isExternalUrl($inline->getUrl())) {
+            $attrs['class'] = 'external-link';
+        }
+
+        return new HtmlElement('a', $attrs, $htmlRenderer->renderInlines($inline->children()));
+    }
+
+    private function isExternalUrl($url)
+    {
+        return parse_url($url, PHP_URL_HOST) !== $this->host;
+    }
+}
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addInlineRenderer(Link::class, new MyCustomLinkRenderer());
+
+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Some inlines can contain other inlines - don’t forget to render those too!
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/customization/overview/index.html b/1.6/customization/overview/index.html new file mode 100644 index 0000000000..2e7ad0939d --- /dev/null +++ b/1.6/customization/overview/index.html @@ -0,0 +1,472 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders you need
  2. +
  3. Set custom configuration options within the Environment
  4. +
  5. Instantiate a DocParser and HtmlRenderer using that Environment
  6. +
  7. Use the DocParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  8. +
  9. Use the HtmlRenderer to convert the AST Document into HTML
  10. +
+ +

The MarkdownConverter class handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->mergeConfig([
+    'html_input' => 'strip',
+]);
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renders convert the parsed blocks/inlines from the AST representation into HTML. There are two types of renderers:

+ + + +

When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you +to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/attributes/index.html b/1.6/extensions/attributes/index.html new file mode 100644 index 0000000000..0a5a47db40 --- /dev/null +++ b/1.6/extensions/attributes/index.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+{#id .class}
+## Header
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the extension
+$environment->addExtension(new AttributesExtension());
+
+// Set your configuration if needed
+$environment->mergeConfig([
+    // ...
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/autolinks/index.html b/1.6/extensions/autolinks/index.html new file mode 100644 index 0000000000..df1dfb4930 --- /dev/null +++ b/1.6/extensions/autolinks/index.html @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Set your configuration if needed
+$environment->mergeConfig([
+    // ...
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a separate Mention extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/commonmark/index.html b/1.6/extensions/commonmark/index.html new file mode 100644 index 0000000000..ce3dc0c9d5 --- /dev/null +++ b/1.6/extensions/commonmark/index.html @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically included for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Or if you call the Environment::createCommonMarkEnvironment() helper:

+ +
use League\CommonMark\DocParser;
+use League\CommonMark\Environment;
+use League\CommonMark\HtmlRenderer;
+
+$environment = Environment::createCommonMarkEnvironment();
+
+$parser = new DocParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderBlock($document);
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Set your configuration if needed
+$environment->mergeConfig([
+    // ...
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/disallowed-raw-html/index.html b/1.6/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..5ec2ad0bf9 --- /dev/null +++ b/1.6/extensions/disallowed-raw-html/index.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically filters certain HTML tags when rendering output, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Set your configuration if needed
+$environment->mergeConfig([
+    // ...
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I cannot change the page <title>anymore</title>');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/external-links/index.html b/1.6/extensions/external-links/index.html new file mode 100644 index 0000000000..a549f0aa26 --- /dev/null +++ b/1.6/extensions/external-links/index.html @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Set your configuration
+$environment->mergeConfig([
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($config, $environment);
+echo $converter->convertToHtml('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +

+use League\CommonMark\ElementRendererInterface;
+use League\CommonMark\HtmlElement;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\Inline\Element\Link;
+use League\CommonMark\Inline\Renderer\InlineRendererInterface;
+
+class MyCustomLinkRenderer implements InlineRendererInterface
+{
+
+    /**
+     * @param Link                     $inline
+     * @param ElementRendererInterface $htmlRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
+    {
+        if (!($inline instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible inline type: ' . \get_class($inline));
+        }
+
+        if ($inline->getData('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to add a custom icon by targeting the html_class given in the configuration:

+ +
$config = [
+    'external_link' => [
+        'html_class' => 'external',
+    ],
+];
+
+ +
/**
+ * Custom SVG Icon.
+ */
+a[target="_blank"]::after,
+a.external::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link External (https://iconify.design/icon-sets/octicon/link-external.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 12 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M11 10h1v3c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h3v1H1v10h10v-3zM6 2l2.25 2.25L5 7.5 6.5 9l3.25-3.25L12 8V2H6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/footnotes/index.html b/1.6/extensions/footnotes/index.html new file mode 100644 index 0000000000..05f913ca9d --- /dev/null +++ b/1.6/extensions/footnotes/index.html @@ -0,0 +1,497 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1">&#8617;</a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Set your configuration
+$environment->mergeConfig([
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/github-flavored-markdown/index.html b/1.6/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..e926a21576 --- /dev/null +++ b/1.6/extensions/github-flavored-markdown/index.html @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+$environment->mergeConfig([]);
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = Environment::createCommonMarkEnvironment();
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+$environment->mergeConfig([]);
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/heading-permalinks/index.html b/1.6/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..bd930078ed --- /dev/null +++ b/1.6/extensions/heading-permalinks/index.html @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Normalizer\SlugNormalizer;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Set your configuration
+$environment->mergeConfig([
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'user-content',
+        'insert' => 'before',
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+        'slug_normalizer' => new SlugNormalizer(),
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

inner_contents (deprecated since 1.5.0)

+ +

This controls the HTML you want to appear inside of the generated <a> tag. Usually this would be something you would +style as some kind of link icon, but you can replace this with any custom HTML you wish.

+ +

This option was deprecated in 1.5.0 and will be removed in 2.0.0. Use the symbol option instead.

+ +

This option has no default value and if one is provided, a deprecation warning will be triggered and the symbol +config option below will be ignored completely.

+ +

See the Upgrade Guide for more information.

+ +

insert

+ +

This controls whether the anchor is added to the beginning of the <h1>, <h2> etc. tag or to the end. Can be set to either 'before' or 'after'.

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

slug_normalizer

+ +

“Slugs” are the strings used within the href, name, and id attributes to identify a particular permalink. +By default, this extension will generate slugs based on the contents of the heading, just like GitHub-Flavored Markdown does.

+ +

You can change the string that is used as the “slug” by setting the slug_normalizer option to any class that implements TextNormalizerInterface.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'heading_permalink' => [
+        // ... other options here ...
+        'slug_normalizer' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'heading_permalink' => [
+        // ... other options here ...
+        'slug_normalizer' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/inlines-only/index.html b/1.6/extensions/inlines-only/index.html new file mode 100644 index 0000000000..9795cd08a2 --- /dev/null +++ b/1.6/extensions/inlines-only/index.html @@ -0,0 +1,424 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Create a new, empty environment
+$environment = new Environment();
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Set any custom configuration options you wish
+$environment->mergeConfig([]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/mentions/index.html b/1.6/extensions/mentions/index.html new file mode 100644 index 0000000000..ed37b9763e --- /dev/null +++ b/1.6/extensions/mentions/index.html @@ -0,0 +1,637 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which symbol prefix you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting symbol prefix, a regular expression pattern to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go.
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Set your configuration.
+$environment->mergeConfig([
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same prefix, the last one registered will
+        // always take precedence.
+        'twitter_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[A-Za-z0-9_]{1,15}(?!\w)',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://twitter.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Prefix └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the prefix (in this case, @). You can use any prefix, regex pattern, or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Inline\Element\AbstractInline;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go.
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Set your configuration.
+$environment->mergeConfig([
+    'mentions' => [
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Set your configuration.
+$environment->mergeConfig([
+    'mentions' => [
+        'user_url_generator' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z0-9]+',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/overview/index.html b/1.6/extensions/overview/index.html new file mode 100644 index 0000000000..77b9182e7f --- /dev/null +++ b/1.6/extensions/overview/index.html @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+$environment->mergeConfig([]);
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = Environment::createCommonMarkEnvironment();
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->mergeConfig([]);
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/smart-punctuation/index.html b/1.6/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..6f2360e067 --- /dev/null +++ b/1.6/extensions/smart-punctuation/index.html @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Set your configuration
+$environment->mergeConfig([
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/strikethrough/index.html b/1.6/extensions/strikethrough/index.html new file mode 100644 index 0000000000..0434420fd5 --- /dev/null +++ b/1.6/extensions/strikethrough/index.html @@ -0,0 +1,423 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/table-of-contents/index.html b/1.6/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..1fb81a353b --- /dev/null +++ b/1.6/extensions/table-of-contents/index.html @@ -0,0 +1,579 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Set your configuration
+$environment->mergeConfig([
+    // Extension defaults are shown below
+    // If you're happy with the defaults, feel free to remove them from this array
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+]);
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/tables/index.html b/1.6/extensions/tables/index.html new file mode 100644 index 0000000000..2e67102aad --- /dev/null +++ b/1.6/extensions/tables/index.html @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/extensions/task-lists/index.html b/1.6/extensions/task-lists/index.html new file mode 100644 index 0000000000..9223e89778 --- /dev/null +++ b/1.6/extensions/task-lists/index.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\Environment;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
+$environment = Environment::createCommonMarkEnvironment();
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convertToHtml($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/index.html b/1.6/index.html new file mode 100644 index 0000000000..8ef2acdfdf --- /dev/null +++ b/1.6/index.html @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/installation/index.html b/1.6/installation/index.html new file mode 100644 index 0000000000..866ce2a9ab --- /dev/null +++ b/1.6/installation/index.html @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +

In your project root just run:

+ +
composer require league/commonmark:^1.6
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^1.6. This is equivalent to >=1.6 <2.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/security/index.html b/1.6/security/index.html new file mode 100644 index 0000000000..7d9dec6dc1 --- /dev/null +++ b/1.6/security/index.html @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convertToHtml($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/support/index.html b/1.6/support/index.html new file mode 100644 index 0000000000..6b6c665f76 --- /dev/null +++ b/1.6/support/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + Support - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Support

+ +

Here are some useful resources to help you use this project:

+ + + +

Supported Versions

+ +

See our security policy for information about the support cycle for bug fixes and security updates.

+ +

Reporting a Vulnerability

+ +

If you discover a security vulnerability within this package, please use the Tidelift security contact form or email Colin O’Dell at colinodell@gmail.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/1.6/upgrading/index.html b/1.6/upgrading/index.html new file mode 100644 index 0000000000..d613dfd57c --- /dev/null +++ b/1.6/upgrading/index.html @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.5 - 1.6 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 1.6. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 1.5 to 1.6

+ +

Configuration changes

+ +

The upcoming v2.0 release is going to change the keys/paths for several configuration options. To help prepare for that change, we’ve added support for the new keys/paths:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current Key/PathNew Key/PathNotes
enable_emcommonmark/enable_em 
enable_strongcommonmark/enable_strong 
use_asteriskcommonmark/use_asterisk 
use_underscorecommonmark/use_underscore 
unordered_list_markerscommonmark/unordered_list_markers 
mentions/*/symbolmentions/*/prefix 
mentions/*/regexmentions/*/patternShould not contain starting/ending / delimiters or flags - must be a partial regex
+ +

Additionally, 2.0 will not support using floats for the max_nesting_level option.

+ +

Version 1.6 will support both the 1.x and 2.0 variations mentioned above but 2.0 won’t, so consider changing them now:

+ +
 $config = [
+     'html_input' => 'escape',
+     'allow_unsafe_links' => false,
+-    'max_nesting_level' => INF,
++    'max_nesting_level' => PHP_INT_MAX,
+     'renderer' => [
+         'block_separator' => "\n",
+         'inner_separator' => "\n",
+         'soft_break'      => "\n",
+     ],
+-    'enable_em' => true,
+-    'enable_strong' => true,
+-    'use_asterisk' => true,
+-    'use_underscore' => true,
+-    'unordered_list_markers' => ['-', '+', '*'],
++    'commonmark' => [
++        'enable_em' => true,
++        'enable_strong' => true,
++        'use_asterisk' => true,
++        'use_underscore' => true,
++        'unordered_list_markers' => ['-', '+', '*'],
++    ],
+     'mentions' => [
+         'github_handle' => [
+-            'symbol'    => '@',
+-            'regex'     => '/[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/i',
++            'prefix'    => '@',
++            'regex'     => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+             'generator' => 'https://github.com/%s',
+         ],
+     ],
+ ];
+
+ +

Converters with custom environments

+ +

Version 2.0 will no longer allow custom environments to be injected via the constructors of CommonMarkConverter or GithubFlavoredMarkdownConverter. You should instead use the newly-added MarkdownConverter class:

+ +
-use League\CommonMark\CommonMarkConverter;
+ use League\CommonMark\Environment;
+ use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
++use League\CommonMark\MarkdownConverter;
+
+ $config = [
+     'html_input' => 'escape',
+     'allow_unsafe_links' => false,
+ ];
+
+ $environment = Environment::createCommonMarkEnvironment();
+ $environment->addExtension(new InlinesOnlyExtension());
++$environment->mergeConfig($config);
+
+ // Go forth and convert you some Markdown!
+-$converter = new CommonMarkConverter($config, $environment);
++$converter = new MarkdownConverter($environment);
+ echo $converter->convertToHtml('# Hello World!');
+
+ +

Environment and Configuration method changes

+ +

The environment’s setConfig() method is now deprecated and will be removed in 2.0 - use mergeConfig() instead.

+ +

Calling ConfigurableEnvironmentInterface::mergeConfig() without the array parameter is deprecated and won’t be allowed in 2.0.

+ +

Calling Configuration::getConfig() without any parameters to retrieve the full configuration is deprecated and won’t be allowed in 2.0. Future versions should only fetch the config items they need, not the whole configuration.

+ +

Calling Configuration::set() without the second $value parameter is deprecated and won’t be allowed in 2.0. You should always explicitly define the value you want to be set.

+ +

RegexHelper::matchAll()

+ +

The RegexHelper::matchAll() method has been deprecated and will be removed in 2.0. Use the new, more-efficient RegexHelper::matchFirst() method instead.

+ +

Extending ArrayCollection

+ +

The ArrayCollection class will be marked final in 2.0 so avoid extending it.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/basic-usage/index.html b/2.0/basic-usage/index.html new file mode 100644 index 0000000000..10a563acee --- /dev/null +++ b/2.0/basic-usage/index.html @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Using Extensions

+ +

The CommonMarkConverter and GithubFlavoredMarkdownConverter shown above automatically configure the environment for you, but if you want to use additional extensions you’ll need to avoid those classes and use the generic MarkdownConverter class instead to customize the environment with whatever extensions you wish to use:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+$environment->addExtension(new InlinesOnlyExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+// <p><strong>Hello World!</strong></p>
+
+ +

Configuration

+ +

If you’re using the CommonMarkConverter or GithubFlavoredMarkdownConverter class you can pass configuration options directly into their constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

See the configuration section for more information on the available configuration options.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ +

Return Value

+ +

The convertToHtml() method actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final HTML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/changelog/index.html b/2.0/changelog/index.html new file mode 100644 index 0000000000..2f2845ab31 --- /dev/null +++ b/2.0/changelog/index.html @@ -0,0 +1,1299 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 2.x releases are shown below. See the full list of releases for the complete changelog.

+ +

2.5.3 - 2024-08-16

+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.1, including: +
      +
    • Remove source, add search to list of recognized block tags
    • +
    +
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.2…2.5.3

+ +

2.5.2 - 2024-08-14

+ +

Changed

+ +
    +
  • Boolean attributes now require an explicit true value (#1040)
  • +
+ +

Fixed

+ +
    +
  • Fixed regression where text could be misinterpreted as an attribute (#1040)
  • +
+ +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.1…2.5.2

+ +

2.5.1 - 2024-07-24

+ +

Fixed

+ +
    +
  • Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
  • +
  • Fixed incorrect Table start line numbers (#1037)
  • +
+ +

New Contributors

+
    +
  • @jasonvarga made their first contribution in https://github.com/thephpleague/commonmark/pull/1035
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.0…2.5.1

+ +

2.5.0 - 2024-07-22

+ +

Added

+ +
    +
  • The AttributesExtension now supports attributes without values (#985, #986)
  • +
  • The AutolinkExtension exposes two new configuration options to override the default behavior (#969, #987): +
      +
    • autolink/allowed_protocols - an array of protocols to allow autolinking for
    • +
    • autolink/default_protocol - the default protocol to use when none is specified
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.0, including: +
      +
    • Allow closing fence to be followed by tabs
    • +
    • Remove restrictive limitation on inline comments
    • +
    • Unicode symbols now treated like punctuation (for purposes of flankingness)
    • +
    • Trailing tabs on the last line of indented code blocks will be excluded
    • +
    • Improved HTML comment matching
    • +
    +
  • +
  • Paragraphs only containing link reference definitions will be kept in the AST until the Document is finalized +
      +
    • (These were previously removed immediately after parsing the Paragraph)
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed list tightness not being determined properly in some edge cases
  • +
  • Fixed incorrect ending line numbers for several block types in various scenarios
  • +
  • Fixed lowercase inline HTML declarations not being accepted
  • +
+ +

New Contributors

+
    +
  • @svenluijten made their first contribution in https://github.com/thephpleague/commonmark/pull/986
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.4…2.5.0

+ +

2.4.4 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.3…2.4.4

+ +

2.4.3 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
  • +
  • Fixed UrlAutolinkParser incorrectly parsing text containing www anywhere before an autolink (#1025)
  • +
+ +

New Contributors

+
    +
  • @nfreader made their first contribution in https://github.com/thephpleague/commonmark/pull/1016
  • +
  • @macbookandrew made their first contribution in https://github.com/thephpleague/commonmark/pull/1025
  • +
  • @xavierlacot made their first contribution in https://github.com/thephpleague/commonmark/pull/1013
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.2…2.4.3

+ +

2.4.2 - 2024-02-02

+ +

Fixed

+ +
    +
  • Fixed declaration parser being too strict
  • +
  • FencedCodeRenderer: don’t add language- to class if already prefixed
  • +
+ +

New Contributors

+
    +
  • @sergiy-petrov made their first contribution in https://github.com/thephpleague/commonmark/pull/997
  • +
  • @clustermin made their first contribution in https://github.com/thephpleague/commonmark/pull/988
  • +
  • @peter279k made their first contribution in https://github.com/thephpleague/commonmark/pull/996
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.1…2.4.2

+ +

2.4.1 - 2023-08-30

+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not fully disabling the rel attribute when configured to do so (#992)
  • +
+ +

2.4.0 - 2023-03-24

+ +

See the upgrading guide for more information about the exception-related changes

+ +

Added

+ +
    +
  • Added generic CommonMarkException marker interface for all exceptions thrown by the library
  • +
  • Added several new specific exception types implementing that marker interface: +
      +
    • AlreadyInitializedException
    • +
    • InvalidArgumentException
    • +
    • IOException
    • +
    • LogicException
    • +
    • MissingDependencyException
    • +
    • NoMatchingRendererException
    • +
    • ParserLogicException
    • +
    +
  • +
  • Added more configuration options to the Heading Permalinks extension (#939): +
      +
    • heading_permalink/apply_id_to_heading - When true, the id attribute will be applied to the heading element itself instead of the <a> tag
    • +
    • heading_permalink/heading_class - class to apply to the heading element
    • +
    • heading_permalink/insert - now accepts none to prevent the creation of the <a> link
    • +
    +
  • +
  • Added new table/alignment_attributes configuration option to control how table cell alignment is rendered (#959)
  • +
+ +

Changed

+ +
    +
  • Change several thrown exceptions from RuntimeException to LogicException (or something extending it), including: +
      +
    • CallbackGenerators that fail to set a URL or return an expected value
    • +
    • MarkdownParser when deactivating the last block parser or attempting to get an active block parser when they’ve all been closed
    • +
    • Adding items to an already-initialized Environment
    • +
    • Rendering a Node when no renderer has been registered for it
    • +
    +
  • +
  • HeadingPermalinkProcessor now throws InvalidConfigurationException instead of RuntimeException when invalid config values are given.
  • +
  • HtmlElement::setAttribute() no longer requires the second parameter for boolean attributes
  • +
  • Several small micro-optimizations
  • +
  • Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
  • +
+ +

Fixed

+ +
    +
  • Fixed inaccurate @throws docblocks throughout the codebase, including ConverterInterface, MarkdownConverter, and MarkdownConverterInterface. +
      +
    • These previously suggested that only \RuntimeExceptions were thrown, which was inaccurate as \LogicExceptions were also possible.
    • +
    +
  • +
+ +

2.3.9 - 2023-02-15

+ +

Fixed

+ +
    +
  • Fixed autolink extension not detecting some URIs with underscores (#956)
  • +
+ +

2.3.8 - 2022-12-10

+ +

Fixed

+ +
    +
  • Fixed parsing issues when mb_internal_encoding() is set to something other than UTF-8 (#951)
  • +
+ +

2.3.7 - 2022-11-17

+ +

Fixed

+ +
    +
  • Fixed TaskListItemMarkerRenderer not including HTML attributes set on the node by other extensions (#947)
  • +
+ +

2.3.6 - 2022-10-30

+ +

Fixed

+ +
    +
  • Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a .) (#943)
  • +
+ +

2.3.5 - 2022-07-29

+ +

Fixed

+ +
    +
  • Fixed error using InlineParserEngine when no inline parsers are registered in the Environment (#908)
  • +
+ +

2.3.4 - 2022-07-17

+ +

Changed

+ +
    +
  • Made a number of small tweaks to the embed extension’s parsing behavior to fix #898: +
      +
    • Changed EmbedStartParser to always capture embed-like lines in container blocks, regardless of parent block type
    • +
    • Changed EmbedProcessor to also remove Embed blocks that aren’t direct children of the Document
    • +
    • Increased the priority of EmbedProcessor to 1010
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed EmbedExtension not parsing embeds following a list block (#898)
  • +
+ +

2.3.3 - 2022-06-07

+ +

Fixed

+ +
    +
  • Fixed DomainFilteringAdapter not reindexing the embed list (#884, #885)
  • +
+ +

2.3.2 - 2022-06-03

+ +

Fixed

+ +
    +
  • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
  • +
+ +

2.2.5 - 2022-06-03

+ +

Fixed

+ +
    +
  • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
  • +
+ +

2.3.1 - 2022-05-14

+ +

Fixed

+ +
    +
  • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
  • +
+ +

2.2.4 - 2022-05-14

+ +

Fixed

+ +
    +
  • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
  • +
+ +

2.3.0 - 2022-04-07

+ +

Added

+ +
    +
  • Added new EmbedExtension (#805)
  • +
  • Added DocumentRendererInterface as a replacement for the now-deprecated MarkdownRendererInterface
  • +
+ +

Deprecated

+ +
    +
  • Deprecated MarkdownRendererInterface; use DocumentRendererInterface instead
  • +
+ +

2.2.3 - 2022-02-26

+ +

Fixed

+ +
    +
  • Fixed front matter parsing with Windows line endings (#821)
  • +
+ +

2.1.3 - 2022-02-26

+ +

Fixed

+ +
    +
  • Fixed front matter parsing with Windows line endings (#821)
  • +
+ +

2.0.4 - 2022-02-26

+ +

Fixed

+ +
    +
  • Fixed front matter parsing with Windows line endings (#821)
  • +
+ +

2.2.2 - 2022-02-13

+ +

Fixed

+ +
    +
  • Fixed double-escaping of image alt text (#806, #810)
  • +
  • Fixed Psalm typehints for event class names
  • +
+ +

2.1.2 - 2022-02-13

+ +

Fixed

+ +
    +
  • Fixed double-escaping of image alt text (#806, #810)
  • +
  • Fixed Psalm typehints for event class names
  • +
+ +

2.0.3 - 2022-02-13

+ +

Fixed

+ +
    +
  • Fixed double-escaping of image alt text (#806, #810)
  • +
  • Fixed Psalm typehints for event class names
  • +
+ +

2.2.1 - 2022-01-25

+ +

Fixed

+ +
    +
  • Fixed symfony/deprecation-contracts constraint
  • +
+ +

Removed

+ +
    +
  • Removed deprecation trigger from MarkdownConverterInterface to reduce noise
  • +
+ +

2.2.0 - 2022-01-22

+ +

Added

+ +
    +
  • Added new ConverterInterface
  • +
  • Added new MarkdownToXmlConverter class
  • +
  • Added new HtmlDecorator class which can wrap existing renderers with additional HTML tags
  • +
  • Added new table/wrap config to apply an optional wrapping/container element around a table (#780)
  • +
+ +

Changed

+ +
    +
  • HtmlElement contents can now consist of any Stringable, not just HtmlElement and string
  • +
+ +

Deprecated

+ +
    +
  • Deprecated MarkdownConverterInterface and its convertToHtml() method; use ConverterInterface and convert() instead
  • +
+ +

2.1.1 - 2022-01-02

+ +

Added

+ +
    +
  • Added missing return type to Environment::dispatch() to fix deprecation warning (#778)
  • +
+ +

2.1.0 - 2021-12-05

+ +

Added

+ +
    +
  • Added support for ext-yaml in FrontMatterExtension (#715)
  • +
  • Added support for symfony/yaml v6.0 in FrontMatterExtension (#739)
  • +
  • Added new heading_permalink/aria_hidden config option (#741)
  • +
+ +

Fixed

+ +
    +
  • Fixed PHP 8.1 deprecation warning (#759, #762)
  • +
+ +

2.0.2 - 2021-08-14

+ +

Changed

+ +
    +
  • Bumped minimum version of league/config to support PHP 8.1
  • +
+ +

Fixed

+ +
    +
  • Fixed ability to register block parsers that identify lines starting with letters (#706)
  • +
+ +

2.0.1 - 2021-07-31

+ +

Fixed

+ +
    +
  • Fixed nested autolinks (#689)
  • +
  • Fixed description lists being parsed incorrectly (#692)
  • +
  • Fixed Table of Contents not respecting Heading Permalink prefixes (#690)
  • +
+ +

2.0.0 - 2021-07-24

+ +

No changes were introduced since the previous 2.0.0-rc2 release.

+ +

Please refer to the full Changelog for a list of all changes between 1.x and 2.0. An upgrading guide is also available.

+ +

2.0.0-rc2 - 2021-07-17

+ +

Fixed

+ +
    +
  • Fixed Mentions inside of links creating nested links against the spec’s rules (#688)
  • +
+ +

2.0.0-rc1 - 2021-07-10

+ +

No changes were introduced since the previous release.

+ +

2.0.0-beta3 - 2021-07-03

+ +

Changed

+ +
    +
  • Any leading UTF-8 BOM will be stripped from the input
  • +
  • The getEnvironment() method of CommonMarkConverter and GithubFlavoredMarkdownConverter will always return the concrete, configurable Environment for upgrading convenience
  • +
  • Optimized AST iteration
  • +
  • Lots of small micro-optimizations
  • +
+ +

2.0.0-beta2 - 2021-06-27

+ +

See https://commonmark.thephpleague.com/2.0/upgrading/ for detailed information on upgrading to version 2.0.

+ +

Added

+ +
    +
  • Added new Node::iterator() method and NodeIterator class for faster AST iteration (#683, #684)
  • +
+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.30.0
  • +
  • Optimized link label parsing
  • +
  • Optimized AST iteration for a 50% performance boost in some event listeners (#683, #684)
  • +
+ +

Fixed

+ +
    +
  • Fixed processing instructions with EOLs
  • +
  • Fixed case-insensitive matching for HTML tag types
  • +
  • Fixed type 7 HTML blocks incorrectly interrupting lazy paragraphs
  • +
  • Fixed newlines in reference labels not collapsing into spaces
  • +
  • Fixed link label normalization with escaped newlines
  • +
  • Fixed unnecessary AST iteration when no default attributes are configured
  • +
+ +

2.0.0-beta1 - 2021-06-20

+ +

See https://commonmark.thephpleague.com/2.0/upgrading/ for detailed information on upgrading to version 2.0.

+ +

Added

+ +
    +
  • Added three new extensions: + +
  • +
  • Added new XmlRenderer to simplify AST debugging (see documentation) (#431)
  • +
  • Added the ability to configure disallowed raw HTML tags (#507)
  • +
  • Added the ability for Mentions to use multiple characters for their symbol (#514, #550)
  • +
  • Added the ability to delegate event dispatching to PSR-14 compliant event dispatcher libraries
  • +
  • Added new configuration options: +
      +
    • Added heading_permalink/min_heading_level and heading_permalink/max_heading_level options to control which headings get permalinks (#519)
    • +
    • Added heading_permalink/fragment_prefix to allow customizing the URL fragment prefix (#602)
    • +
    • Added footnote/backref_symbol option for customizing backreference link appearance (#522)
    • +
    • Added slug_normalizer/max_length option to control the maximum length of generated URL slugs
    • +
    • Added slug_normalizer/unique option to control whether unique slugs should be generated per-document or per-environment
    • +
    +
  • +
  • Added purity markers throughout the codebase (verified with Psalm)
  • +
  • Added Query class to simplify Node traversal when looking to take action on certain Nodes
  • +
  • Added new HtmlFilter and StringContainerHelper utility classes
  • +
  • Added new AbstractBlockContinueParser class to simplify the creation of custom block parsers
  • +
  • Added several new classes and interfaces: +
      +
    • BlockContinue
    • +
    • BlockContinueParserInterface
    • +
    • BlockContinueParserWithInlinesInterface
    • +
    • BlockStart
    • +
    • BlockStartParserInterface
    • +
    • ChildNodeRendererInterface
    • +
    • ConfigurableExtensionInterface
    • +
    • CursorState
    • +
    • DashParser (extracted from PunctuationParser)
    • +
    • DelimiterParser
    • +
    • DocumentBlockParser
    • +
    • DocumentPreRenderEvent
    • +
    • DocumentRenderedEvent
    • +
    • EllipsesParser (extracted from PunctuationParser)
    • +
    • ExpressionInterface
    • +
    • FallbackNodeXmlRenderer
    • +
    • InlineParserEngineInterface
    • +
    • InlineParserMatch
    • +
    • MarkdownParserState
    • +
    • MarkdownParserStateInterface
    • +
    • MarkdownRendererInterface
    • +
    • Query
    • +
    • RawMarkupContainerInterface
    • +
    • ReferenceableInterface
    • +
    • RenderedContent
    • +
    • RenderedContentInterface
    • +
    • ReplaceUnpairedQuotesListener
    • +
    • SpecReader
    • +
    • TableOfContentsRenderer
    • +
    • UniqueSlugNormalizer
    • +
    • UniqueSlugNormalizerInterface
    • +
    • XmlRenderer
    • +
    • XmlNodeRendererInterface
    • +
    +
  • +
  • Added several new methods: +
      +
    • Cursor::getCurrentCharacter()
    • +
    • Environment::createDefaultConfiguration()
    • +
    • Environment::setEventDispatcher()
    • +
    • EnvironmentInterface::getExtensions()
    • +
    • EnvironmentInterface::getInlineParsers()
    • +
    • EnvironmentInterface::getSlugNormalizer()
    • +
    • FencedCode::setInfo()
    • +
    • Heading::setLevel()
    • +
    • HtmlRenderer::renderDocument()
    • +
    • InlineParserContext::getFullMatch()
    • +
    • InlineParserContext::getFullMatchLength()
    • +
    • InlineParserContext::getMatches()
    • +
    • InlineParserContext::getSubMatches()
    • +
    • LinkParserHelper::parsePartialLinkLabel()
    • +
    • LinkParserHelper::parsePartialLinkTitle()
    • +
    • Node::assertInstanceOf()
    • +
    • RegexHelper::isLetter()
    • +
    • StringContainerInterface::setLiteral()
    • +
    • TableCell::getType()
    • +
    • TableCell::setType()
    • +
    • TableCell::getAlign()
    • +
    • TableCell::setAlign()
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Changed the converter return type +
      +
    • CommonMarkConverter::convertToHtml() now returns an instance of RenderedContentInterface. This can be cast to a string for backward compatibility with 1.x.
    • +
    +
  • +
  • Table of Contents items are no longer wrapped with <p> tags (#613)
  • +
  • Heading Permalinks now link to element IDs instead of using name attributes (#602)
  • +
  • Heading Permalink IDs and URL fragments now have a content prefix by default (#602)
  • +
  • Changes to configuration options: +
      +
    • enable_em has been renamed to commonmark/enable_em
    • +
    • enable_strong has been renamed to commonmark/enable_strong
    • +
    • use_asterisk has been renamed to commonmark/use_asterisk
    • +
    • use_underscore has been renamed to commonmark/use_underscore
    • +
    • unordered_list_markers has been renamed to commonmark/unordered_list_markers
    • +
    • mentions/*/symbol has been renamed to mentions/*/prefix
    • +
    • mentions/*/regex has been renamed to mentions/*/pattern and requires partial regular expressions (without delimiters or flags)
    • +
    • max_nesting_level now defaults to PHP_INT_MAX and no longer supports floats
    • +
    • heading_permalink/slug_normalizer has been renamed to slug_normalizer/instance
    • +
    +
  • +
  • Event dispatching is now fully PSR-14 compliant
  • +
  • Moved and renamed several classes - see the full list here
  • +
  • The HeadingPermalinkExtension and FootnoteExtension were modified to ensure they never produce a slug which conflicts with slugs created by the other extension
  • +
  • SlugNormalizer::normalizer() now supports optional prefixes and max length options passed in via the $context argument
  • +
  • The AbstractBlock::$data and AbstractInline::$data arrays were replaced with a Data array-like object on the base Node class
  • +
  • Implemented a new approach to block parsing. This was a massive change, so here are the highlights: +
      +
    • Functionality previously found in block parsers and node elements has moved to block parser factories and block parsers, respectively (more details)
    • +
    • ConfigurableEnvironmentInterface::addBlockParser() is now EnvironmentBuilderInterface::addBlockParserFactory()
    • +
    • ReferenceParser was re-implemented and works completely different than before
    • +
    • The paragraph parser no longer needs to be added manually to the environment
    • +
    +
  • +
  • Implemented a new approach to inline parsing where parsers can now specify longer strings or regular expressions they want to parse (instead of just single characters): +
      +
    • InlineParserInterface::getCharacters() is now getMatchDefinition() and returns an instance of InlineParserMatch
    • +
    • InlineParserContext::__construct() now requires the contents to be provided as a Cursor instead of a string
    • +
    +
  • +
  • Implemented delimiter parsing as a special type of inline parser (via the new DelimiterParser class)
  • +
  • Changed block and inline rendering to use common methods and interfaces +
      +
    • BlockRendererInterface and InlineRendererInterface were replaced by NodeRendererInterface with slightly different parameters. All core renderers now implement this interface.
    • +
    • ConfigurableEnvironmentInterface::addBlockRenderer() and addInlineRenderer() were combined into EnvironmentBuilderInterface::addRenderer()
    • +
    • EnvironmentInterface::getBlockRenderersForClass() and getInlineRenderersForClass() are now just getRenderersForClass()
    • +
    +
  • +
  • Completely refactored the Configuration implementation +
      +
    • All configuration-specific classes have been moved into a new league/config package with a new namespace
    • +
    • Configuration objects must now be configured with a schema and all options must match that schema - arbitrary keys are no longer permitted
    • +
    • Configuration::__construct() no longer accepts the default configuration values - use Configuration::merge() instead
    • +
    • ConfigurationInterface now only contains a get(string $key); this method no longer allows arbitrary default values to be returned if the option is missing
    • +
    • ConfigurableEnvironmentInterface was renamed to EnvironmentBuilderInterface
    • +
    • ExtensionInterface::register() now requires an EnvironmentBuilderInterface param instead of ConfigurableEnvironmentInterface
    • +
    +
  • +
  • Added missing return types to virtually every class and interface method
  • +
  • Re-implemented the GFM Autolink extension using the new inline parser approach instead of document processors +
      +
    • EmailAutolinkProcessor is now EmailAutolinkParser
    • +
    • UrlAutolinkProcessor is now UrlAutolinkParser
    • +
    +
  • +
  • HtmlElement can now properly handle array (i.e. class) and boolean (i.e. checked) attribute values
  • +
  • HtmlElement automatically flattens any attributes with array values into space-separated strings, removing duplicate entries
  • +
  • Combined separate classes/interfaces into one: +
      +
    • DisallowedRawHtmlRenderer replaces DisallowedRawHtmlBlockRenderer and DisallowedRawHtmlInlineRenderer
    • +
    • NodeRendererInterface replaces BlockRendererInterface and InlineRendererInterface
    • +
    +
  • +
  • Renamed the following methods: +
      +
    • Environment and ConfigurableEnvironmentInterface: +
        +
      • addBlockParser() is now addBlockStartParser()
      • +
      +
    • +
    • ReferenceMap and ReferenceMapInterface: +
        +
      • addReference() is now add()
      • +
      • getReference() is now get()
      • +
      • listReferences() is now getIterator()
      • +
      +
    • +
    • Various node (block/inline) classes: +
        +
      • getContent() is now getLiteral()
      • +
      • setContent() is now setLiteral()
      • +
      +
    • +
    +
  • +
  • Moved and renamed the following constants: +
      +
    • EnvironmentInterface::HTML_INPUT_ALLOW is now HtmlFilter::ALLOW
    • +
    • EnvironmentInterface::HTML_INPUT_ESCAPE is now HtmlFilter::ESCAPE
    • +
    • EnvironmentInterface::HTML_INPUT_STRIP is now HtmlFilter::STRIP
    • +
    • TableCell::TYPE_HEAD is now TableCell::TYPE_HEADER
    • +
    • TableCell::TYPE_BODY is now TableCell::TYPE_DATA
    • +
    +
  • +
  • Changed the visibility of the following properties: +
      +
    • AttributesInline::$attributes is now private
    • +
    • AttributesInline::$block is now private
    • +
    • TableCell::$align is now private
    • +
    • TableCell::$type is now private
    • +
    • TableSection::$type is now private
    • +
    +
  • +
  • Several methods which previously returned $this now return void +
      +
    • Delimiter::setPrevious()
    • +
    • Node::replaceChildren()
    • +
    • Context::setTip()
    • +
    • Context::setContainer()
    • +
    • Context::setBlocksParsed()
    • +
    • AbstractStringContainer::setContent()
    • +
    • AbstractWebResource::setUrl()
    • +
    +
  • +
  • Several classes are now marked final: +
      +
    • ArrayCollection
    • +
    • Emphasis
    • +
    • FencedCode
    • +
    • Heading
    • +
    • HtmlBlock
    • +
    • HtmlElement
    • +
    • HtmlInline
    • +
    • IndentedCode
    • +
    • Newline
    • +
    • Strikethrough
    • +
    • Strong
    • +
    • Text
    • +
    +
  • +
  • Heading nodes no longer directly contain a copy of their inner text
  • +
  • StringContainerInterface can now be used for inlines, not just blocks
  • +
  • ArrayCollection only supports integer keys
  • +
  • HtmlElement now implements Stringable
  • +
  • Cursor::saveState() and Cursor::restoreState() now use CursorState objects instead of arrays
  • +
  • NodeWalker::next() now enters, traverses any children, and leaves all elements which may have children (basically all blocks plus any inlines with children). Previously, it only did this for elements explicitly marked as “containers”.
  • +
  • InvalidOptionException was removed
  • +
  • Anything with a getReference(): ReferenceInterface method now implements ReferencableInterface
  • +
  • The SmartPunct extension now replaces all unpaired Quote elements with Text elements towards the end of parsing, making the QuoteRenderer unnecessary
  • +
  • Several changes made to the Footnote extension: +
      +
    • Footnote identifiers can no longer contain spaces
    • +
    • Anonymous footnotes can now span subsequent lines
    • +
    • Footnotes can now contain multiple lines of content, including sub-blocks, by indenting them
    • +
    • Footnote event listeners now have numbered priorities (but still execute in the same order)
    • +
    • Footnotes must now be separated from previous content by a blank line
    • +
    +
  • +
  • The line numbers (keys) returned via MarkdownInput::getLines() now start at 1 instead of 0
  • +
  • DelimiterProcessorCollectionInterface now extends Countable
  • +
  • RegexHelper::PARTIAL_ constants must always be used in case-insensitive contexts
  • +
  • HeadingPermalinkProcessor no longer accepts text normalizers via the constructor - these must be provided via configuration instead
  • +
  • Blocks which can’t contain inlines will no longer be asked to render inlines
  • +
  • AnonymousFootnoteRefParser and HeadingPermalinkProcessor now implement EnvironmentAwareInterface instead of ConfigurationAwareInterface
  • +
  • The second argument to TextNormalizerInterface::normalize() must now be an array
  • +
  • The title attribute for Link and Image nodes is now stored using a dedicated property instead of stashing it in $data
  • +
  • ListData::$delimiter now returns either ListBlock::DELIM_PERIOD or ListBlock::DELIM_PAREN instead of the literal delimiter
  • +
+ +

Fixed

+ +
    +
  • Fixed parsing of footnotes without content
  • +
  • Fixed rendering of orphaned footnotes and footnote refs
  • +
  • Fixed some URL autolinks breaking too early (#492)
  • +
  • Fixed AbstractStringContainer not actually being abstract
  • +
+ +

Removed

+ +
    +
  • Removed support for PHP 7.1, 7.2, and 7.3 (#625, #671)
  • +
  • Removed all previously-deprecated functionality: +
      +
    • Removed the ability to pass custom Environment instances into the CommonMarkConverter and GithubFlavoredMarkdownConverter constructors
    • +
    • Removed the Converter class and ConverterInterface
    • +
    • Removed the bin/commonmark script
    • +
    • Removed the Html5Entities utility class
    • +
    • Removed the InlineMentionParser (use MentionParser instead)
    • +
    • Removed DefaultSlugGenerator and SlugGeneratorInterface from the Extension/HeadingPermalink/Slug sub-namespace (use the new ones under ./SlugGenerator instead)
    • +
    • Removed the following ArrayCollection methods: +
        +
      • add()
      • +
      • set()
      • +
      • get()
      • +
      • remove()
      • +
      • isEmpty()
      • +
      • contains()
      • +
      • indexOf()
      • +
      • containsKey()
      • +
      • replaceWith()
      • +
      • removeGaps()
      • +
      +
    • +
    • Removed the ConfigurableEnvironmentInterface::setConfig() method
    • +
    • Removed the ListBlock::TYPE_UNORDERED constant
    • +
    • Removed the CommonMarkConverter::VERSION constant
    • +
    • Removed the HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant
    • +
    • Removed the heading_permalink/inner_contents configuration option
    • +
    +
  • +
  • Removed now-unused classes: +
      +
    • AbstractStringContainerBlock
    • +
    • BlockRendererInterface
    • +
    • Context
    • +
    • ContextInterface
    • +
    • Converter
    • +
    • ConverterInterface
    • +
    • InlineRendererInterface
    • +
    • PunctuationParser (was split into two classes: DashParser and EllipsesParser)
    • +
    • QuoteRenderer
    • +
    • UnmatchedBlockCloser
    • +
    +
  • +
  • Removed the following methods, properties, and constants: +
      +
    • AbstractBlock::$open
    • +
    • AbstractBlock::$lastLineBlank
    • +
    • AbstractBlock::isContainer()
    • +
    • AbstractBlock::canContain()
    • +
    • AbstractBlock::isCode()
    • +
    • AbstractBlock::matchesNextLine()
    • +
    • AbstractBlock::endsWithBlankLine()
    • +
    • AbstractBlock::setLastLineBlank()
    • +
    • AbstractBlock::shouldLastLineBeBlank()
    • +
    • AbstractBlock::isOpen()
    • +
    • AbstractBlock::finalize()
    • +
    • AbstractBlock::getData()
    • +
    • AbstractInline::getData()
    • +
    • ConfigurableEnvironmentInterface::addBlockParser()
    • +
    • ConfigurableEnvironmentInterface::mergeConfig()
    • +
    • Delimiter::setCanClose()
    • +
    • EnvironmentInterface::getConfig()
    • +
    • EnvironmentInterface::getInlineParsersForCharacter()
    • +
    • EnvironmentInterface::getInlineParserCharacterRegex()
    • +
    • HtmlRenderer::renderBlock()
    • +
    • HtmlRenderer::renderBlocks()
    • +
    • HtmlRenderer::renderInline()
    • +
    • HtmlRenderer::renderInlines()
    • +
    • Node::isContainer()
    • +
    • RegexHelper::matchAll() (use the new matchFirst() method instead)
    • +
    • RegexHelper::REGEX_WHITESPACE
    • +
    +
  • +
  • Removed the second $contents argument from the Heading constructor
  • +
+ +

Deprecated

+ +

The following things have been deprecated and will not be supported in v3.0:

+ +
    +
  • Environment::mergeConfig() (set configuration before instantiation instead)
  • +
  • Environment::createCommonMarkEnvironment() and Environment::createGFMEnvironment() +
      +
    • Alternative 1: Use CommonMarkConverter or GithubFlavoredMarkdownConverter if you don’t need to customize the environment
    • +
    • Alternative 2: Instantiate a new Environment and add the necessary extensions yourself
    • +
    +
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/configuration/index.html b/2.0/configuration/index.html new file mode 100644 index 0000000000..19973f7801 --- /dev/null +++ b/2.0/configuration/index.html @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

Many aspects of this library’s behavior can be tweaked using configuration options.

+ +

You can provide an array of configuration options to the Environment or converter classes when creating them:

+ +
$config = [
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'commonmark' => [
+        'enable_em' => true,
+        'enable_strong' => true,
+        'use_asterisk' => true,
+        'use_underscore' => true,
+        'unordered_list_markers' => ['-', '*', '+'],
+    ],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => PHP_INT_MAX,
+    'slug_normalizer' => [
+        'max_length' => 255,
+    ],
+];
+
+ +

If you’re using the basic CommonMarkConverter or GithubFlavoredMarkdown classes, simply pass the configuration array into the constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

Here’s a list of the core configuration options available:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: PHP_INT_MAX). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested.
  • +
  • slug_normalizer - Array of options for configuring how URL-safe slugs are created; see the slug normalizer docs for more details +
      +
    • instance - An alternative normalizer to use (defaults to the included SlugNormalizer)
    • +
    • max_length - Limits the size of generated slugs (defaults to 255 characters)
    • +
    • unique - Controls whether slugs should be unique per 'document' (default) or per 'environment'; can be disabled with false
    • +
    +
  • +
+ +

Additional configuration options are available for most of the available extensions - refer to their individual documentation for more details. For example, the CommonMark core extension offers these additional options:

+ +
    +
  • commonmark - Array of options for configuring the CommonMark core extension: +
      +
    • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
    • +
    • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
    • +
    • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
    • +
    • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
    • +
    • unordered_list_markers - Array of characters that can be used to indicate a bulleted list (default: ["-", "*", "+"])
    • +
    +
  • +
+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/abstract-syntax-tree/index.html b/2.0/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..cca90877e2 --- /dev/null +++ b/2.0/customization/abstract-syntax-tree/index.html @@ -0,0 +1,702 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the MarkdownParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent see the (Event Dispatcher documentation)
  • +
+ +

Visualization

+ +

Even with an interactive debugger it can be tricky to view an entire tree at once. Consider using the XmlRenderer to provide a simple text-based representation of the AST for debugging purposes.

+ +

Node Traversal

+ +

There are four different ways to traverse/iterate the Nodes within the AST:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodProsCons
Manual TraversalBest way to access/check direct relatives of nodesNot useful for iteration
Iterating the TreeFast and efficientPossible unexpected behavior when adding/removing sibling nodes while iterating
Walking the TreeFull control over iterationUp to twice as slow as iteration; adding/removing nodes while iterating can lead to weird behaviors
Querying NodesEasier to write and understand; no weird behaviorsNot memory efficient
+ +

Each is described in more detail below

+ +

Manual Traversal

+ +

The following methods can be used to manually traverse from one Node to any of its direct relatives:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

This is best suited for situations when you need to know information about those relatives.

+ +

Iterating the Tree

+ +

If you’d like to iterate through all the nodes, use the iterator() method to obtain an iterator that will loop through each node in the tree (using pre-order traversal):

+ +
foreach ($document->iterator() as $node) {
+    echo 'Current node: ' . get_class($node) . "\n";
+}
+
+ +

Given an AST like this (XML representation):

+ +
<document>
+  <heading level="1">
+    <text>Hello World!</text>
+  </heading>
+  <paragraph>
+    <text>This is an example of </text>
+    <strong>
+      <text>CommonMark</text>
+    </strong>
+    <text>.</text>
+  </paragraph>
+</document>
+
+ +

The code above will output:

+ +
Current node: League\CommonMark\Node\Block\Document
+Current node: League\CommonMark\Extension\CommonMark\Node\Block\Heading
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Block\Paragraph
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Extension\CommonMark\Node\Inline\Strong
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Inline\Text
+
+ +

This iterator doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes. It’s also very CPU and memory-efficient.

+ +

Be careful when modifying nodes while iterating the tree as some of those changes may affect the current iteration process, especially for sibling nodes that come after the current one. For example, if you remove the current node’s next() sibling, the next loop of that iteration will still include the removed sibling even though it was successfully removed from the AST. Similarly, any new siblings that are added won’t be visited on the next loop.

+ +

Walking the Tree

+ +

If you’d like to walk through all the nodes, visiting each one as you enter and leave it, use the walker() method to obtain an instance of NodeWalker. This also uses pre-order traversal but emitting NodeWalkerEvents along the way:

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'Now ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

Using the same example AST in the previous section, this code will output:

+ +
Now entering a League\CommonMark\Node\Block\Document node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Block\Paragraph node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Node\Block\Paragraph node
+Now leaving a League\CommonMark\Node\Block\Document node
+
+ +

This approach offers many of the same benefits as the simple iteration shown in the previous section such as memory efficiency and no recursion. The key differences come from how you enter and leave nodes:

+ +
    +
  1. Iteration can potentially take twice as long - not ideal for performance
  2. +
  3. Provides you with more control over exactly when an action is taken on a node which is sometimes needed for certain AST manipulations
  4. +
  5. Also provides a resumeAt() method to override where it should iterate next
  6. +
+ +

But like with the iterator, be careful when adding/removing nodes while walking the tree, as there are even more subtle cases where the walker could even lose track of where it was, which may result in some nodes being visited multiple times or not at all.

+ +

Querying Nodes

+ +

If you’re trying to locate certain nodes to perform actions on them, querying the nodes from the AST might be easier to implement. This can be done with the Query class:

+ +
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Node\Block\Paragraph;
+use League\CommonMark\Node\Query;
+
+// Find all paragraphs and blockquotes that contain links
+$matchingNodes = (new Query())
+    ->where(Query::type(Paragraph::class))
+    ->orWhere(Query::type(BlockQuote::class))
+    ->andWhere(Query::hasChild(Query::type(Link::class)))
+    ->findAll($document);
+
+foreach ($matchingNodes as $node) {
+    // TODO: Do something with them
+}
+
+ +

Each condition passed into where(), orWhere(), or andWhere() must be a callable “filter” that accepts a Node and returns true or false. We provide several methods that can help create these filters for you:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Query::type(string $class)Creates a filter that matches nodes with the given class name
Query::hasChild()Creates a filter that matches nodes which contain at least one child
Query::hasChild(callable $condition)Creates a filter that matches nodes which contain at least one child that matches the inner $condition
Query::hasParent()Creates a filter that matches nodes which have a parent
Query::hasParent(callable $condition)Creates a filter that matches nodes which have a parent that matches the inner $condition
+ +

You can of course create your own custom filters/conditions using an anonymous function or by implementing ExpressionInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Node\Query;
+use League\CommonMark\Node\Query\ExpressionInterface;
+
+class ChildCountGreaterThan implements ExpressionInterface
+{
+    private $count;
+
+    public function __construct(int $count)
+    {
+        $this->count = $count;
+    }
+
+    public function __invoke(Node $node) : bool{
+        return count($node->children()) > $this->count;
+    }
+}
+
+$query = (new Query())
+    ->where(function (Node $node): bool { return $node->data->has('attributes/class'); })
+    ->andWhere(new ChildCountGreaterThan(3));
+
+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ +

Data Storage

+ +

Each Node has a property called data which is a Data (array-like) object. This can be used to store any arbitrary data you’d like on the node:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text1 = new Text('Hello, world!');
+$text1->data->set('language', 'English');
+$text1->data->set('is_good_translation', true);
+
+$text2 = new Text('Bonjour monde!');
+$text2->data->set('language', 'French');
+$text2->data->set('is_good_translation', false);
+
+foreach ([$text1, $text2] as $text) {
+    if ($text->data->get('is_good_translation')) {
+        sprintf('In %s we would say: "%s"', $text->data->get('language'), $text->getLiteral());
+    } else {
+        sprintf('I think they would say "%s" in %s, but I\'m not sure.', $text->getLiteral(), $text->data->get('language'));
+    }
+}
+
+ +

You can also access deeply-nested paths using / or . as delimiters:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text = new Text('Hello, world!');
+$text->data->set('info', ['language' => 'English', 'is_good_translation' => true]);
+
+var_dump($text->data->get('info/language'));
+var_dump($text->data->get('info.is_good_translation'));
+
+$text->data->set('info/is_example', true);
+
+ +

HTML Attributes

+ +

The data property comes pre-instantiated with a single data element called attributes which is used to store any HTML attributes that need to be rendered. For example:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+$link = new Link('https://twitter.com/colinodell', '@colinodell');
+$link->data->append('attributes/class', 'social-link');
+$link->data->append('attributes/class', 'twitter');
+$link->data->set('attributes/target', '_blank');
+$link->data->set('attributes/rel', 'noopener');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/block-parsing/index.html b/2.0/customization/block-parsing/index.html new file mode 100644 index 0000000000..f22dd9ef0c --- /dev/null +++ b/2.0/customization/block-parsing/index.html @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

At a high level, block parsing is a two-step process:

+ +
    +
  1. Using a BlockStartParserInterface to identify if/where a block start exists on the given line
  2. +
  3. Using a BlockContinueParserInterface to perform additional processing of the identified block
  4. +
+ +

So to implement a custom block parser you will actually need to implement both of these classes.

+ +

BlockStartParserInterface

+ +

Instances of this interface have a single tryStart() method:

+ +
/**
+ * Check whether we should handle the block at the current position
+ *
+ * @param Cursor                       $cursor
+ * @param MarkdownParserStateInterface $parserState
+ *
+ * @return BlockStart|null
+ */
+public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart;
+
+ +

Given a Cursor at the current position, plus some extra information about the state of the parser, this method is responsible for determining whether a particular type of block seems to exist at the given position. You don’t actually parse the block here - that’s the job of a BlockContinueParserInterface. Your only job here is to return whether or not a particular type of block does exist here, and if so which block parser should parse it.

+ +

If you find that you cannot parse the given block, you should return BlockStart::none(); from this function.

+ +

However, if the Markdown at the current position does indeed seem to be the type of block you’re looking for, you should return a BlockStart instance using the following static constructor pattern:

+ +
use League\CommonMark\Parser\Block\BlockStart;
+
+return BlockStart::of(new MyCustomParser())->at($cursor);
+
+ +

Unlike in 1.x, the Cursor state is no longer shared between parsers. You must therefore explicitly provide the BlockStart object with a copy of your cursor at the correct, post-parsing position.

+ +

NOTE: If your custom block starts with a letter character you’ll need to add your parser to the environment with a priority of 250 or higher. This is due to a performance optimization where such lines are usually skipped.

+ +

BlockContinueParserInterface

+ +

The previous interface only helps the engine identify where a block starts. Additional information about the block, as well as the ability to parse additional lines of input, is all handled by the BlockContinueParserInterface.

+ +

This interface has several methods, so it’s usually easier to extend from AbstractBlockContinueParser instead, which sets most of the methods to use typical defaults you can override as needed.

+ +

getBlock()

+ +
public function getBlock(): AbstractBlock;
+
+ +

Each instance of a BlockContinueParserInterface is associated with a new block that is being parsed. This method here returns that block.

+ +

isContainer()

+ +
public function isContainer(): bool;
+
+ +

This method returns whether or not the block is a “container” capable of containing other blocks as children.

+ +

canContain()

+ +
public function canContain(AbstractBlock $childBlock): bool;
+
+ +

This method returns whether the current block being parsed can contain the given child block.

+ +

canHaveLazyContinuationLines()

+ +
public function canHaveLazyContinuationLines(): bool;
+
+ +

This method returns whether or not this parser should also receive subsequent lines of Markdown input. This is primarily used when a block can span multiple lines, like code blocks do.

+ +

addLine()

+ +
public function addLine(string $line): void;
+
+ +

If canHaveLazyContinuationLines() returned true, this method will be called with the additional lines of content.

+ +

tryContinue()

+ +
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue;
+
+ +

This method allows you to try and parse an additional line of Markdown.

+ +

closeBlock()

+ +
public function closeBlock(): void;
+
+ +

This method is called when the block is done being parsed. Any final adjustments to the block should be made at this time.

+ +

parseInlines()

+ +
public function parseInlines(InlineParserEngineInterface $inlineParser): void;
+
+ +

This method is called when the engine is ready to parse any inline child elements.

+ +

Note: For performance reasons, this method is not part of BlockContinueParserInterface. If your block may contain inlines, you should make sure that your “continue parser” also implements BlockContinueParserWithInlinesInterface.

+ +

Tips

+ +

Here are some additional tips to consider when writing your own custom parsers:

+ +

Combining both into one file

+ +

Although parsing requires two classes, you can use the anonymous class feature of PHP to combine both into a single file! Here’s an example:

+ +
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
+use League\CommonMark\Parser\Block\BlockStartParserInterface;
+
+final class MyCustomBlockParser extends AbstractBlockContinueParser
+{
+    // TODO: implement your continuation parsing methods here
+
+    public static function createBlockStartParser(): BlockStartParserInterface
+    {
+        return new class implements BlockStartParserInterface
+        {
+            // TODO: implement the tryStart() method here
+        };
+    }
+}
+
+
+ +

Performance

+ +

The BlockStartParserInterface::tryStart() and BlockContinueParserInterface::tryContinue() methods may be called hundreds or thousands of times during execution. For best performance, have your methods return as early as possible, and make sure your code is highly optimized.

+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

If your block contains literal strings/text within the block (and not as part of a child block), you should have your custom block type also implement StringContainerInterface.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/configuration/index.html b/2.0/customization/configuration/index.html new file mode 100644 index 0000000000..a7f76db83f --- /dev/null +++ b/2.0/customization/configuration/index.html @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Configuration Schemas and Values

+ +

Version 2.0 introduced a new robust system for defining configuration schemas and accessing them within custom extensions.

+ +

Configuration Schemas

+ +

Unlike in 1.x, all configuration options must have a defined schema. This defines which options are available, what types of values they accept, whether any are required, and any default values you wish to define if the user doesn’t provide any.

+ +

These custom options can be defined from within your custom extension by implementing the ConfigurableExtensionInterface:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+use Nette\Schema\Expect;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        $builder->addSchema('my_extension', Expect::structure([
+            'enable_some_feature' => Expect::bool()->default(true),
+            'html_class' => Expect::string()->default('my-custom-extension'),
+            'align' => Expect::anyOf('left', 'center', 'right')->default('left'),
+            'favorite_number' => Expect::int()->min(1)->max(100)->default(42),
+        ]));
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        // TODO: Implement register() method
+    }
+}
+
+ +

See the league/config documentation for more examples of how to define custom configuration schemas.

+ +

Note that you only need to implement ConfigurableExtensionInterface if you plan to define new configuration options - you don’t need this if you’re only reading existing options.

+ +

Reading Configuration Values

+ +

Okay, so your extension has defined the different options that are available, but now you want to start using them within your custom extension. There are a few ways you can access the values:

+ +

During Extension Registration

+ +

Perhaps your extension needs to decide whether/how to register certain parsers/renderers/etc based on the user-provided configuration values - in that case, you can read the value from the $environment - for example:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        // (see code example above)
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        if ($environment->getConfiguration()->get('my_extension/enable_some_feature')) {
+            $environment->addBlockStartParser(new MyCustomParser());
+            $environment->addRenderer(MyCustomBlockType::class, new MyCustomRenderer());
+        }
+    }
+}
+
+ +

Within Parsers/Renderers/Listeners

+ +

Perhaps you want to reference those configuration values from within a custom parser, renderer, event listener, or something else. This can easily by done by having that class also implement ConfigurationAwareInterface. This interface signals to the Environment that your class needs a copy of the final configuration so it can read it later:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\Config\ConfigurationAwareInterface;
+use League\Config\ConfigurationInterface;
+
+final class MyCustomRenderer implements NodeRendererInterface, ConfigurationAwareInterface
+{
+    /**
+     * @var ConfigurationInterface
+     */
+    private $config;
+
+    public function setConfiguration(ConfigurationInterface $configuration): void
+    {
+        $this->config = $configuration;
+    }
+
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return 'My favorite number is ' . $this->config->get('my_extension/favorite_number');
+    }
+}
+
+ +

You can access any configuration value from here, not just the ones you might have defined yourself.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/cursor/index.html b/2.0/customization/cursor/index.html new file mode 100644 index 0000000000..a4e1ea0a21 --- /dev/null +++ b/2.0/customization/cursor/index.html @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Parser\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter(int $index)Returns the character at the given absolute position
getCurrentCharacter()Returns the character at the current position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/delimiter-processing/index.html b/2.0/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..611f280972 --- /dev/null +++ b/2.0/customization/delimiter-processing/index.html @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void;
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
+
+// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
use League\CommonMark\Delimiter\Delimiter;
+use League\CommonMark\Node\Inline\Text;
+
+$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/environment/index.html b/2.0/customization/environment/index.html new file mode 100644 index 0000000000..5b5b194594 --- /dev/null +++ b/2.0/customization/environment/index.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all extensions, parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

An empty Environment can be obtained like this:

+ +
use League\CommonMark\Environment\Environment;
+
+$config = [];
+$environment = new Environment($config);
+
+ +

You can customize the Environment using any of the methods below (from the EnvironmentBuilderInterface interface).

+ +

Once your Environment is configured with whatever configuration and extensions you want, you can instantiate a MarkdownConverter and start converting MD to HTML:

+ +
use League\CommonMark\MarkdownConverter;
+
+// Using $environment from the previous code sample
+$converter = new MarkdownConverter($environment);
+
+echo $converter->convertToHtml('# Hello World!');
+
+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. For example, if you want core CommonMark functionality plus footnote support:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+
+$config = [];
+$environment = new Environment($config);
+
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new FootnoteExtension());
+
+ +

addBlockStartParser()

+ +
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockStartParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addRenderer()

+ +
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0);
+
+ +

Registers a NodeRendererInterface to handle a specific type of AST node ($nodeClass) with the given priority (a higher number will be executed earlier).

+ +

See Rendering for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ +

Accessing the Environment and Configuration within parsers/renderers/etc

+ +

If your custom parser/renderer/listener/etc. implements either EnvironmentAwareInterface or ConfigurationAwareInterface we’ll automatically inject the environment or configuration into them once the environment has been fully initialized. This will provide your code with access to the finalized information it may need.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/event-dispatcher/index.html b/2.0/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..3d49112827 --- /dev/null +++ b/2.0/customization/event-dispatcher/index.html @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic, PSR-14-compliant event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code.

+ +

Event Class

+ +

Any PSR-14 compliant event can be used, though we also provide an AbstractEvent class you can use to easily create your own events:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - the event object to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

League\CommonMark\Event\DocumentPreRenderEvent

+ +

This event is dispatched by the renderer just before rendering begins. Like with DocumentParsedEvent, this offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering, but with the added knowledge of which format is being rendered to (e.g. html).

+ +

League\CommonMark\Event\DocumentRenderedEvent

+ +

This event is dispatched once the rendering step has been completed, just before the output is returned. The final output can be adjusted at this point or additional metadata can be attached to the return object.

+ +

Bring Your Own PSR-14 Event Dispatcher

+ +

Although this library provides PSR-14 compliant event dispatching out-of-the-box, you may want to use your own PSR-14 event dispatcher instead. This is possible as long as that third-party library both:

+ +
    +
  1. Implements the PSR-14 EventDispatcherInterface; and,
  2. +
  3. Allows you to register additional ListenerProviderInterface instances with that dispatcher library
  4. +
+ +

Not all libraries support this so please check carefully! Assuming yours does, delegating all the event behavior to that library can be done with two steps:

+ +

First, call the setEventDispatcher() method on the Environment to register that other implementation. With that done, any calls to Environment::dispatch() will be passed through to that other dispatcher. But we still need to let that dispatcher know about the events registered by CommonMark extensions, otherwise nothing will happen when events are dispatched.

+ +

Because the Environment implements PSR-14’s ListenerProviderInterface you’ll also need to pass the configured Environment object to your event dispatcher so that it becomes aware of those available events.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event): void
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data->append('attributes/class', 'external-link');
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfiguration()->get('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = new Environment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convertToHtml($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/extensions/index.html b/2.0/customization/extensions/index.html new file mode 100644 index 0000000000..4279292fff --- /dev/null +++ b/2.0/customization/extensions/index.html @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a EnvironmentBuilderInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new EmojiExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/inline-parsing/index.html b/2.0/customization/inline-parsing/index.html new file mode 100644 index 0000000000..b8e47dde4a --- /dev/null +++ b/2.0/customization/inline-parsing/index.html @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getMatchDefinition()

+ +

This method should return an instance of InlineParserMatch which defines the text the parser is looking for. Examples of this might be something like:

+ +
use League\CommonMark\Parser\Inline\InlineParserMatch;
+
+InlineParserMatch::string('@');                  // Match any '@' characters found in the text
+InlineParserMatch::string('foo');                // Match the text 'foo' (case insensitive)
+
+InlineParserMatch::oneOf('@', '!');              // Match either character
+InlineParserMatch::oneOf('http://', 'https://'); // Match either string
+
+InlineParserMatch::regex('\d+');                 // Match the regular expression (omit the regex delimiters and any flags)
+
+ +

Once a match is found, the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has found at a matching string in the current line; and,
  2. +
  3. No other inline parsers with a higher priority have successfully parsed the text at this point in the line
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser - see more information below.
  • +
+ +
InlineParserContext
+ +

This class has several useful methods:

+ +
    +
  • getContainer() - Returns the current container block the inline text was found in. You’ll almost always call $inlineContext->getContainer()->appendChild(...) to add the parsed inline text inside that block.
  • +
  • getReferenceMap() - Returns the document’s reference map
  • +
  • getCursor() - Returns the current Cursor used to parse the current line. (Note that the cursor will be positioned before the matched text, so you must advance it yourself if you determine it’s a valid match)
  • +
  • getDelimiterStack() - Returns the current delimiter stack. Only used in advanced use cases.
  • +
  • getFullMatch() - Returns the full string that matched you InlineParserMatch definition
  • +
  • getFullMatchLength() - Returns the length of the full match - useful for advancing the cursor
  • +
  • getSubMatches() - If your InlineParserMatch used a regular expression with capture groups, this will return the text matches by those groups.
  • +
  • getMatches() - Returns an array where index 0 is the “full match”, plus any sub-matches. It basically simulates preg_match()’s behavior.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the text at the current position for any reason. Other parsers will then have a chance to try parsing that text. If all registered parsers return false, the text will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed/matched text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::regex('@([A-Za-z0-9_]{1,15}(?!\w))');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+
+        // This seems to be a valid match
+        // Advance the cursor to the end of the match
+        $cursor->advanceBy($inlineContext->getFullMatchLength());
+
+        // Grab the Twitter handle
+        [$handle] = $inlineContext->getSubMatches();
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+// And here's how to hook it up:
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::oneOf(':)', ':(');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($inlineContext->getFullMatch() === ':)') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($inlineContext->getFullMatch() === ':(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance: +
      +
    • Avoid using overly-complex regular expressions in getMatchDefinition() - use the simplest regex you can and have parse() do the heavier validation
    • +
    • Have your parse() method return false as soon as possible.
    • +
    +
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/overview/index.html b/2.0/customization/overview/index.html new file mode 100644 index 0000000000..115c3b166f --- /dev/null +++ b/2.0/customization/overview/index.html @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders/configuration you need
  2. +
  3. Instantiate a MarkdownParser and HtmlRenderer using that Environment
  4. +
  5. Use the MarkdownParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  6. +
  7. Use the HtmlRenderer to convert the AST Document into HTML
  8. +
+ +

The MarkdownConverter class handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Renderer\HtmlRenderer;
+
+$environment = new Environment([
+    'html_input' => 'strip',
+]);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderDocument($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renderers convert the parsed blocks/inlines from the AST representation into HTML. When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/rendering/index.html b/2.0/customization/rendering/index.html new file mode 100644 index 0000000000..8f2bf400e0 --- /dev/null +++ b/2.0/customization/rendering/index.html @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + + Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Custom Rendering

+ +

Renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement NodeRendererInterface and its render() method. Note that in v2.0, both +block renderers and inline renderers share the same interface and method:

+ +

render()

+ +
public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The HtmlRenderer will call this method during the rendering process whenever a supported element is encountered.

+ +

If your renderer can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • Node $node - The encountered block or inline element that needs to be rendered
  • +
  • ChildNodeRendererInterface $childRenderer - If the given $node has children, use this to render those child elements
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the node and its contents, including any children. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\Util\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Renderers

+ +

When registering your renderer, you must tell the Environment which node element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// First param - the node class type that should use our renderer
+// Second param - instance of the renderer
+$environment->addRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple types:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
+use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements NodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(ThematicBreak::class, new TextDividerRenderer());
+
+ +

Note that thematic breaks should not contain children, which is why the $childRenderer is unused in this example. Otherwise we’d have to call code like this and return the result as part of the rendered HTML we’re generating here: $innerHtml = $childRenderer->renderNodes($node->children());

+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any child elements that your node might contain!
  • +
+ +

XML Rendering

+ +

The XML renderer will automatically attempt to convert any AST nodes to XML by inspecting the name of the block/inline node and its attributes. You can instead control the XML element name and attributes by making your renderer implement XmlNodeRendererInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+use League\CommonMark\Xml\XmlNodeRendererInterface;
+
+class TextDividerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+
+    public function getXmlTagName(Node $node): string
+    {
+        return 'text_divider';
+    }
+
+    public function getXmlAttributes(Node $node): array
+    {
+        return ['character' => '='];
+    }
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/customization/slug-normalizer/index.html b/2.0/customization/slug-normalizer/index.html new file mode 100644 index 0000000000..61cb7561d9 --- /dev/null +++ b/2.0/customization/slug-normalizer/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + Slug Normalizer - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Slug Normalizer

+ +

“Slugs” are strings used within href, name, and id HTML attributes to identify particular elements within a document.

+ +

Some extensions (like the HeadingPermalinkExtension) need the ability to convert user-provided text into these URL-safe slugs while also ensuring that these are unique throughout the generated HTML. The Environment provides a pre-built normalizer you can use for this purpose.

+ +

Usage

+ +

You can obtain a reference to the built-in slug normalizer by calling $environment->getSlugNormalizer();

+ +

To use this within your extension, have your parser/renderer/whatever implement EnvironmentAwareInterface and then implement the corresponding setEnvironment method like this:

+ +

+use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Environment\EnvironmentAwareInterface;
+
+class MyCustomParserOrRenderer implements EnvironmentAwareInterface
+{
+    private $slugNormalizer;
+
+    public function setEnvironment(EnvironmentInterface $environment): void
+    {
+        $this->slugNormalizer = $environment->getSlugNormalizer();
+    }
+}
+
+ +

You can then call $this->slugNormalizer->normalize($text) as needed.

+ +

Configuration

+ +

The slug_normalizer configuration section allows you to adjust the following options:

+ +

instance

+ +

You can change the string that is used as the “slug” by setting the instance option to any class that implements TextNormalizerInterface. +We provide a simple SlugNormalizer by default, but you may want to plug in a different library or create your own normalizer instead.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

max_length

+ +

This can be configured to limit the length of that slug to prevent overly-long values. By default, that limit is 255 characters. You may set this to any positive integer, or 0 for no limit.

+ +

(Note that generated slugs might be slightly longer than this “limit” if the unique option is enabled and the slug generator detects a duplicate slug and needs to add a suffix to make it unique.)

+ +

unique

+ +

This options controls whether slugs should be unique. Possible values include:

+ +
    +
  • 'document' (string; default) - Ensures slugs are unique within a single document
  • +
  • 'environment' (string) - Ensures slugs are unique across multiple documents - see below
  • +
  • false (boolean) - Disables unique slug generation
  • +
+ +

You might have a use case where you’re converting several different Markdown documents on the same page and so you’d like to ensure that none of those documents use conflicting slugs. In that case, you should set the scope option to 'environment' to ensure that a single instance of a MarkdownConverter (which uses a single Environment) will never produce the same slug twice during its lifetime (which usually lasts the entire duration of a single HTTP request).

+ +

If you need complete control over how unique slugs are generated, make your 'instance' implement UniqueSlugNormalizerInterface; otherwise, we’ll simply append incremental numbers to slugs to ensure they are unique.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/attributes/index.html b/2.0/extensions/attributes/index.html new file mode 100644 index 0000000000..cefe2f1d67 --- /dev/null +++ b/2.0/extensions/attributes/index.html @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+ +

This results in the following output:

+ +
<blockquote title="Blockquote title">
+<p>A nice blockquote</p>
+</blockquote>
+
+ +

CSS-selector-style declarations can be used to set the id and class attributes:

+ +
{#id .class}
+## Header
+
+ +

Output:

+ +
<h2 class="class" id="id">Header</h2>
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Output:

+ +
<p>This is <em style="color: red">red</em>.</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/autolinks/index.html b/2.0/extensions/autolinks/index.html new file mode 100644 index 0000000000..7a4d7ce8af --- /dev/null +++ b/2.0/extensions/autolinks/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a Mention Parser outside of this extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/commonmark/index.html b/2.0/extensions/commonmark/index.html new file mode 100644 index 0000000000..bf8bda0b61 --- /dev/null +++ b/2.0/extensions/commonmark/index.html @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically installed for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new Environment with the core extension
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/default-attributes/index.html b/2.0/extensions/default-attributes/index.html new file mode 100644 index 0000000000..1457cba9fa --- /dev/null +++ b/2.0/extensions/default-attributes/index.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + Default Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Default Attributes

+ +

The DefaultAttributesExtension allows you to apply default HTML classes and other attributes using configuration options.

+ +

It works by applying the attributes to the nodes during the DocumentParsedEvent event - right after the nodes are parsed but before they are rendered. +(As a result, it’s possible that renderers may add other attributes - the goal of this extension is only to provide custom defaults.)

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DefaultAttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Node\Block\Paragraph;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'default_attributes' => [
+        Heading::class => [
+            'class' => static function (Heading $node) {
+                if ($node->getLevel() === 1) {
+                    return 'title-main';
+                } else {
+                    return null;
+                }
+            },
+        ],
+        Table::class => [
+            'class' => 'table',
+        ],
+        Paragraph::class => [
+            'class' => ['text-center', 'font-comic-sans'],
+        ],
+        Link::class => [
+            'class' => 'btn btn-link',
+            'target' => '_blank',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new DefaultAttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a default_attributes array. Each key in the array should be a FQCN for the node class you wish to apply the default attribute to, and the values should be a map of attribute names to attribute values.

+ +

Attribute values may be any of the following types:

+ +
    +
  • string
  • +
  • string[]
  • +
  • bool
  • +
  • callable (parameter is the Node, return value may be string|string[]|bool)
  • +
+ +

Examples

+ +

Here’s an example that will apply Bootstrap 4 classes and attributes:

+ +
$config = [
+    'default_attributes' => [
+        Table::class => [
+            'class' => ['table', 'table-responsive'],
+        ],
+        BlockQuote::class => [
+            'class' => 'blockquote',
+        ],
+    ],
+];
+
+ +

Here’s a more complex example that uses a callable to add a class only if the paragraph immediately follows an <h1> heading:

+ +
$config = [
+    'default_attributes' => [
+        Paragraph::class => [
+            'class' => static function (Paragraph $paragraph) {
+                if ($paragraph->previous() instanceof Heading && $paragraph->previous()->getLevel() === 1) {
+                    return 'lead';
+                }
+
+                return null;
+            },
+        ],
+    ],
+];
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/description-lists/index.html b/2.0/extensions/description-lists/index.html new file mode 100644 index 0000000000..40c72ae61a --- /dev/null +++ b/2.0/extensions/description-lists/index.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + Description List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Description List Extension

+ +

The DescriptionListExtension adds Markdown Extra-style description lists to facilitate the creation of <dl>, <dt>, and <dd> HTML using Markdown.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DescriptionListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\DescriptionList\DescriptionListExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DescriptionListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Some markdown goes here');
+
+ +

Syntax

+ +

The syntax is based directly on the rules and logic implemented by the Markdown Extra library. Here are some examples of sample Markdown input and HTML output demonstrating the syntax:

+ +
Apple
+:   Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.
+:   An American computer company.
+
+Orange
+:   The fruit of an evergreen tree of the genus Citrus.
+
+ +
<dl>
+    <dt>Apple</dt>
+    <dd>Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.</dd>
+    <dd>An American computer company.</dd>
+
+    <dt>Orange</dt>
+    <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
+</dl>
+
+ +

See the Markdown Extra documentation or our own spec for additional examples.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/disallowed-raw-html/index.html b/2.0/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..ada37bbaf7 --- /dev/null +++ b/2.0/extensions/disallowed-raw-html/index.html @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically escapes certain HTML tags when rendering raw HTML, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Customize the extension's configuration if needed
+// Default values are shown below - you can omit this configuration if you're happy with those defaults
+// and don't want to customize them
+$config = [
+    'disallowed_raw_html' => [
+        'disallowed_tags' => ['title', 'textarea', 'style', 'xmp', 'iframe', 'noembed', 'noframes', 'script', 'plaintext'],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I cannot change the page <title>anymore</title>');
+
+ +

Configuration

+ +

This extension can be configured by providing a disallowed_raw_html array with the following nested configuration options. The defaults are shown in the code example above.

+ +

disallowed_tags

+ +

An array containing a list of tags that should be escaped.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/external-links/index.html b/2.0/extensions/external-links/index.html new file mode 100644 index 0000000000..dfdc14c8f3 --- /dev/null +++ b/2.0/extensions/external-links/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class MyCustomLinkRenderer implements NodeRendererInterface
+{
+    /**
+     * @param Node                       $node
+     * @param ChildNodeRendererInterface $childRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        if (!($node instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible node type: ' . \get_class($node));
+        }
+
+        if ($node->data->get('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to automagically add an external link icon by targeting the html_class given in the configuration:

+ +
// Font Awesome example:
+a[target="_blank"]::after,
+a.external::after {
+   content: "\f08e";
+   font: normal normal normal 14px/1 FontAwesome;
+}
+
+// Glyphicon example:
+a[target="_blank"]::after,
+a.external::after {
+  @extend .glyphicon;
+  content: "\e164";
+  margin-left: .5em;
+  margin-right: .25em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/footnotes/index.html b/2.0/extensions/footnotes/index.html new file mode 100644 index 0000000000..d4838ba298 --- /dev/null +++ b/2.0/extensions/footnotes/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1"></a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'backref_symbol'     => '↩',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

backref_symbol

+ +

This string option sets the symbol used as the contents of the footnote backreference link. It defaults to \League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer::DEFAULT_SYMBOL = '↩'.

+ +

If you want to use a custom icon, set this to an empty string '' and take a look at the Adding Icons section below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ +

Adding Icons

+ +

You can use CSS to add a custom icon instead of providing a backref_symbol:

+ +
$config = [
+    'footnote' => [
+        'backref_class' => 'footnote-backref',
+        'backref_symbol' => '',
+    ],
+];
+
+ +

Then target the backref_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.footnote-backref::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/front-matter/index.html b/2.0/extensions/front-matter/index.html new file mode 100644 index 0000000000..30116ca566 --- /dev/null +++ b/2.0/extensions/front-matter/index.html @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + Front Matter Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Front Matter Extension

+ +

The FrontMatterExtension adds the ability to parse YAML front matter from the Markdown document and include that in the return result.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

You will also need to install symfony/yaml or the YAML extension for PHP to use this extension. For symfony/yaml:

+ +
composer require symfony/yaml
+
+ +

(You can use any version of symfony/yaml 2.3 or higher, though we recommend using 4.0 or higher.)

+ +

Front Matter Syntax

+ +

This extension follows the Jekyll Front Matter syntax. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Here is a basic example:

+ +
---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+
+ +

This will produce a front matter array similar to this:

+ +
$parsedFrontMatter = [
+    'layout' => 'post',
+    'title' => 'I Love Markdown',
+    'tags' => [
+        'test',
+        'example',
+    ],
+];
+
+ +

And the HTML output will only contain the one heading:

+ +
<h1>Hello World!</h1>
+
+ +

Usage

+ +

Configure your Environment as usual and add the FrontMatterExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FrontMatterExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+// A sample Markdown file with some front matter:
+$markdown = <<<MD
+---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+MD;
+
+$result = $converter->convertToHtml($markdown);
+
+// Grab the front matter:
+if ($result instanceof RenderedContentWithFrontMatter) {
+    $frontMatter = $result->getFrontMatter();
+}
+
+// Output the HTML using any of these:
+echo $result;               // implicit string cast
+// or:
+echo (string) $result;      // explicit string cast
+// or:
+echo $result->getContent();
+
+ +

Parsing Front Matter Only

+ +

You don’t have to parse the entire file (including all the Markdown) if you only want the front matter. You can either instantiate the front matter parser yourself and call it directly, like this:

+ +
use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+// For `symfony/yaml`
+$frontMatterParser = new FrontMatterParser(new SymfonyYamlFrontMatterParser());
+// For YAML extension
+$frontMatterParser = new FrontMatterParser(new LibYamlFrontMatterParser());
+$result = $frontMatterParser->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

Or you can use the getFrontMatterParser() method from the extension:

+ +
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+$frontMatterExtension = new FrontMatterExtension();
+$result = $frontMatterExtension->getFrontMatterParser()->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

This latter approach may be more convenient if you have already instantiated a FrontMatterExtension object you’re adding to the Environment somewhere and just want to call that.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/github-flavored-markdown/index.html b/2.0/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..ec863f93ee --- /dev/null +++ b/2.0/extensions/github-flavored-markdown/index.html @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark and GFM parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/heading-permalinks/index.html b/2.0/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..a50c205150 --- /dev/null +++ b/2.0/extensions/heading-permalinks/index.html @@ -0,0 +1,608 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\MarkdownConverter;
+
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'content',
+        'fragment_prefix' => 'content',
+        'insert' => 'before',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

fragment_prefix

+ +

This should be a string you want prepended to the URL fragment in the link’s href attribute. This should typically be set to the same value as id_prefix for links to work properly. However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to “rewrite” them.

+ +

For example, to emulate how GitHub heading permalinks work, set id_prefix to 'user-content', set fragment_prefix to '', and insert some JavaScript into the page like this:

+ +
var scrollToPermalink = function() {
+    var link = document.getElementById('user-content-' + window.location.hash);
+    if (link) {
+        link.scrollIntoView({behavior: 'smooth'});
+    }
+};
+
+window.addEventListener('hashchange', scrollToPermalink);
+if (window.location.hash) {
+    scrollToPermalink();
+}
+
+ +

insert

+ +

This controls whether the anchor is added to the beginning of the <h1>, <h2> etc. tag or to the end. Can be set to either 'before' or 'after'.

+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should have permalinks added. By default, all 6 levels (1, 2, 3, 4, 5, and 6) will have them. You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/inlines-only/index.html b/2.0/extensions/inlines-only/index.html new file mode 100644 index 0000000000..7ab4a2f0c3 --- /dev/null +++ b/2.0/extensions/inlines-only/index.html @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new, empty environment
+$environment = new Environment($config);
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/mentions/index.html b/2.0/extensions/mentions/index.html new file mode 100644 index 0000000000..f9d5c6b8fe --- /dev/null +++ b/2.0/extensions/mentions/index.html @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which prefix you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting prefix, a regular expression to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same prefix, the first mention parser to
+        // successfully match and return a properly constructed Mention object (where the URL has been set) will be the
+        // the mention parser that is used. In this example, the GitHub handle would actually match first because
+        // there isn't any real validation to check whether https://www.github.com/colinodell exists. However, in
+        // CMS applications, you could check whether its a local user first, then check Twitter and then GitHub, etc.
+        'twitter_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[A-Za-z0-9_]{1,15}(?!\w)',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Follow me on GitHub: @colinodell');
+// Output:
+// <p>Follow me on GitHub: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Prefix └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the prefix (in this case, @). You can use any prefix, regular expression pattern (without opening/closing delimiter or modifiers), or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Node\Inline\AbstractInline;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Inline\Element\AbstractInline;
+
+class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'user_url_generator' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z0-9]+',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/overview/index.html b/2.0/extensions/overview/index.html new file mode 100644 index 0000000000..40542e9ae2 --- /dev/null +++ b/2.0/extensions/overview/index.html @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Default AttributesEasily apply default HTML classes using configuration options to match your site’s styles2.0.0 
Description ListsCreate <dl> description lists using Markdown Extra’s syntax2.0.0 
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
Front MatterParses YAML front matter from your Markdown input2.0.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the extensions you need
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the other extensions you need
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/smart-punctuation/index.html b/2.0/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..657ea78853 --- /dev/null +++ b/2.0/extensions/smart-punctuation/index.html @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/strikethrough/index.html b/2.0/extensions/strikethrough/index.html new file mode 100644 index 0000000000..a36493a1e6 --- /dev/null +++ b/2.0/extensions/strikethrough/index.html @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/table-of-contents/index.html b/2.0/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..79ea1e1f57 --- /dev/null +++ b/2.0/extensions/table-of-contents/index.html @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/tables/index.html b/2.0/extensions/tables/index.html new file mode 100644 index 0000000000..a1d222d448 --- /dev/null +++ b/2.0/extensions/tables/index.html @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/extensions/task-lists/index.html b/2.0/extensions/task-lists/index.html new file mode 100644 index 0000000000..9e6f977905 --- /dev/null +++ b/2.0/extensions/task-lists/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convertToHtml($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/index.html b/2.0/index.html new file mode 100644 index 0000000000..89b0d0a012 --- /dev/null +++ b/2.0/index.html @@ -0,0 +1,443 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello, World!')->getContent();
+
+// <h1>Hello, World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/installation/index.html b/2.0/installation/index.html new file mode 100644 index 0000000000..2c1f1f0d1c --- /dev/null +++ b/2.0/installation/index.html @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +
composer require "league/commonmark:^2.0"
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^2.0. This is equivalent to >=2.0 <3.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/security/index.html b/2.0/security/index.html new file mode 100644 index 0000000000..de84646be6 --- /dev/null +++ b/2.0/security/index.html @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convertToHtml($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/support/index.html b/2.0/support/index.html new file mode 100644 index 0000000000..c4b29ea275 --- /dev/null +++ b/2.0/support/index.html @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + Support - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Support

+ +

Here are some useful resources to help you use this project:

+ + + +

Supported Versions

+ +

See our security policy for information about the support cycle for bug fixes and security updates.

+ +

Reporting a Vulnerability

+ +

If you discover a security vulnerability within this package, please use the Tidelift security contact form or email Colin O’Dell at colinodell@gmail.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/upgrading/consumers/index.html b/2.0/upgrading/consumers/index.html new file mode 100644 index 0000000000..126377d78b --- /dev/null +++ b/2.0/upgrading/consumers/index.html @@ -0,0 +1,706 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.6 to 2.0 (for developers) - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Minimum PHP Version

+ +

The minimum supported PHP version was increased from 7.1 to 7.4.

+ +

CommonMarkConverter and GithubFlavoredMarkdownConverter constructors

+ +

The constructor methods for both CommonMarkConverter and GithubFlavoredMarkdownConverter no longer accept passing in a customized Environment. If you want to customize the extensions used in your converter you should switch to using MarkdownConverter. See the Basic Usage documentation for an example.

+ +
-use League\CommonMark\CommonMarkConverter;
+ use League\CommonMark\Environment\Environment;
+ use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+ use League\CommonMark\Extension\Table\TableExtension;
++use League\CommonMark\MarkdownConverter;
+ 
+ $config = [
+     'html_input' => 'escape',
+     'allow_unsafe_links' => false,
+     'max_nesting_level' => 100,
+ ];
+ 
+ 
+-$environment = new Environment();
++$environment = new Environment($config);
+ $environment->addExtension(new CommonMarkCoreExtension());
+ $environment->addExtension(new TableExtension());
+ 
+-$converter = new CommonMarkConverter($config, $environment); // or GithubFlavoredMarkdownConverter
++$converter = new MarkdownConverter($environment);
+ 
+ echo $converter->convertToHtml('Hello World!');
+
+ +

CommonMarkConverter Return Type

+ +

In 1.x, calling convertToHtml() would return a string. In 2.x this changed to return a RenderedContentInterface. To get the resulting HTML, either cast it to a string or call ->getContent(). (This new interface extends from Stringable so you can type hint against that instead, if needed.)

+ +
 use League\CommonMark\CommonMarkConverter;
+
+ $converter = new CommonMarkConverter();
+-echo $converter->convertToHtml('# Hello World!');
++echo $converter->convertToHtml('# Hello World!')->getContent();
++// or
++echo (string) $converter->convertToHtml('# Hello World!');
+
+ +

HTML Changes

+ +

Table of Contents items are no longer wrapped with <p> tags:

+ +
 <ul class="table-of-contents">
+     <li>
+-        <p><a href="#level-2-heading">Level 2 Heading</a></p>
++        <a href="#level-2-heading">Level 2 Heading</a>
+     </li>
+     <li>
+-        <p><a href="#level-4-heading">Level 4 Heading</a></p>
++        <a href="#level-4-heading">Level 4 Heading</a>
+     </li>
+     <li>
+-        <p><a href="#level-3-heading">Level 3 Heading</a></p>
++        <a href="#level-3-heading">Level 3 Heading</a>
+     </li>
+ </ul>
+
+ +

See #613 for details.

+ +

Additionally, the HTML (including URL fragments) for Heading Permalinks have changed:

+ +
-<h1><a id="user-content-hello-world" href="#hello-world" name="hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>Hello World!</h1>
++<h1><a id="content-hello-world" href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>Hello World!</h1>
+
+ +

Note that the href now targets the id attribute instead of the name, which is deprecated in HTML 5. Additionally, the default prefix has changed to content. See the Heading Permalink extension documentation for more details on how to configure the prefixes.

+ +

Configuration Option Changes

+ +

Several configuration options now have new names:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Key/PathNew Key/PathNotes
enable_emcommonmark/enable_em 
enable_strongcommonmark/enable_strong 
use_asteriskcommonmark/use_asterisk 
use_underscorecommonmark/use_underscore 
unordered_list_markerscommonmark/unordered_list_markersEmpty arrays no longer allowed
heading_permalink/id_prefix(unchanged)Default is now content
heading_permalink/inner_contentsheading_permalink/symbol 
heading_permalink/slug_normalizerslug_normalizer/instance 
max_nesting_level(unchanged)Only integer values are supported
mentions/*/symbolmentions/*/prefix 
mentions/*/regexmentions/*/patternCannot contain start/end / delimiters
+ +

Classes/Namespaces Renamed

+ +

Many classes now live in different namespaces, and some have also been renamed. Here’s a quick guide showing their new locations:

+ +

(Note that the base namespace of League\CommonMark has been omitted from this table for brevity.)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Class Namespace/Name (1.x)New Class Namespace/Name (2.0)
EnvironmentEnvironment\Environment
Extension\CommonMarkCoreExtensionExtension\CommonMark\CommonMarkCoreExtension
Block\Element\DocumentNode\Block\Document
DocParserParser\MarkdownParser
DocParserInterfaceParser\MarkdownParserInterface
ElementRendererInterfaceRenderer\ChildNodeRendererInterface
HtmlRendererRenderer\HtmlRenderer
+ +

(This is only a partial list of the clases and interfaces you’re likely to work with as a consumer – see the developer upgrade guide for the complete list.)

+ +

Removed Classes

+ +

The following classes have been removed:

+ + + + + + + + + + + + + + + + + + +
Class name in 1.xReplacement / Notes
ConverterUse MarkdownConverter instead.
ConverterInterfaceUse MarkdownConverterInterface. This interface has the same methods so it should be a drop-in replacement.
+ +

(Several other classes were removed, but these are the only ones you’re likely to notice. See the developer upgrade guide for the complete list.)

+ +

Renamed constants

+ +

The following constants have been moved/renamed:

+ + + + + + + + + + + + + + + + + + + + + + +
Old Name/Location (1.x)New Name/Location (2.0)
EnvironmentInterface::HTML_INPUT_ALLOWHtmlFilter::ALLOW
EnvironmentInterface::HTML_INPUT_ESCAPEHtmlFilter::ESCAPE
EnvironmentInterface::HTML_INPUT_STRIPHtmlFilter::STRIP
+ +

Renamed Methods

+ +

The following methods have been renamed:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassOld Name (1.x)New Name (2.0)
Environment / ConfigurableEnvironmentInterfaceaddBlockParser()addBlockStartParser()
ReferenceMap / ReferenceMapInterfaceaddReference()add()
ReferenceMap / ReferenceMapInterfacegetReference()get()
ReferenceMap / ReferenceMapInterfacelistReferences()getIterator()
+ +

Configuration Method Changes

+ +

Calling EnvironmentInterface::getConfig() without any parameters is no longer supported.

+ +

Calling ConfigurableEnvironmentInterface::mergeConfig() without any parameters is no longer supported.

+ +

The ConfigurableEnvironmentInterface::setConfig() method has been removed. Use getConfig() instead.

+ +

bin/commonmark command

+ +

This command was buggy to test and was relatively unpopular, so it has been removed. If you need this type of functionality, consider writing your own script with a Converter/Environment configured exactly how you want it.

+ +

CommonMarkConverter::VERSION constant

+ +

This previously-deprecated constant was removed in 2.0. Use \Composer\InstalledVersions provided by composer-runtime-api instead.

+ +

HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant

+ +

This previously-deprecated constant was removed in 2.0. Use HeadingPermalinkRenderer::DEFAULT_SYMBOL instead.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/upgrading/developers/index.html b/2.0/upgrading/developers/index.html new file mode 100644 index 0000000000..ee6d61a907 --- /dev/null +++ b/2.0/upgrading/developers/index.html @@ -0,0 +1,1348 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.6 to 2.0 (for developers) - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Minimum PHP Version

+ +

The minimum supported PHP version was increased from 7.1 to 7.4.

+ +

CommonMarkConverter and GithubFlavoredMarkdownConverter constructors

+ +

The constructor methods for both CommonMarkConverter and GithubFlavoredMarkdownConverter no longer accept passing in a customized Environment. If you want to customize the extensions used in your converter you should switch to using MarkdownConverter. See the Basic Usage documentation for an example.

+ +
-use League\CommonMark\CommonMarkConverter;
+ use League\CommonMark\Environment\Environment;
+ use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+ use League\CommonMark\Extension\Table\TableExtension;
++use League\CommonMark\MarkdownConverter;
+ 
+ $config = [
+     'html_input' => 'escape',
+     'allow_unsafe_links' => false,
+     'max_nesting_level' => 100,
+ ];
+ 
+-$environment = new Environment();
++$environment = new Environment($config);
+ $environment->addExtension(new CommonMarkCoreExtension());
+ $environment->addExtension(new TableExtension());
+ 
+-$converter = new CommonMarkConverter($config, $environment); // or GithubFlavoredMarkdownConverter
++$converter = new MarkdownConverter($environment);
+ 
+ echo $converter->convertToHtml('Hello World!');
+
+ +

CommonMarkConverter Return Type

+ +

In 1.x, calling convertToHtml() would return a string. In 2.x this changed to return a RenderedContentInterface. To get the resulting HTML, either cast the return value to a string or call ->getContent(). (This new interface extends from Stringable so you can type hint against that instead, if needed.)

+ +
 use League\CommonMark\CommonMarkConverter;
+
+ $converter = new CommonMarkConverter();
+-echo $converter->convertToHtml('# Hello World!');
++echo $converter->convertToHtml('# Hello World!')->getContent();
++// or
++echo (string) $converter->convertToHtml('# Hello World!');
+
+ +

HTML Changes

+ +

Table of Contents items are no longer wrapped with <p> tags:

+ +
 <ul class="table-of-contents">
+     <li>
+-        <p><a href="#level-2-heading">Level 2 Heading</a></p>
++        <a href="#level-2-heading">Level 2 Heading</a>
+     </li>
+     <li>
+-        <p><a href="#level-4-heading">Level 4 Heading</a></p>
++        <a href="#level-4-heading">Level 4 Heading</a>
+     </li>
+     <li>
+-        <p><a href="#level-3-heading">Level 3 Heading</a></p>
++        <a href="#level-3-heading">Level 3 Heading</a>
+     </li>
+ </ul>
+
+ +

See #613 for details.

+ +

Additionally, the HTML (including URL fragments) for Heading Permalinks have changed:

+ +
-<h1><a id="user-content-hello-world" href="#hello-world" name="hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>Hello World!</h1>
++<h1><a id="content-hello-world" href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>Hello World!</h1>
+
+ +

Note that the href now targets the id attribute instead of the name, which is deprecated in HTML 5. Additionally, the default prefix has changed to content. See the Heading Permalink extension documentation for more details on how to configure the prefixes.

+ +

Configuration Option Changes

+ +

Several configuration options now have new names:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Key/PathNew Key/PathNotes
enable_emcommonmark/enable_em 
enable_strongcommonmark/enable_strong 
use_asteriskcommonmark/use_asterisk 
use_underscorecommonmark/use_underscore 
unordered_list_markerscommonmark/unordered_list_markersEmpty arrays no longer allowed
heading_permalink/id_prefix(unchanged)Default is now content
heading_permalink/inner_contentsheading_permalink/symbol 
heading_permalink/slug_normalizerslug_normalizer/instance 
max_nesting_level(unchanged)Only integer values are supported
mentions/*/symbolmentions/*/prefix 
mentions/*/regexmentions/*/patternCannot contain start/end / delimiters
+ +

Method Return Types

+ +

Return types have been added to virtually all class and interface methods. If you implement or extend anything from this library, ensure you also have the proper return types added.

+ +

Configuration Classes Relocated

+ +

The following classes have been moved to the league/config package:

+ + + + + + + + + + + + + + + + + + + + + + +
Old Class Namespace/Name (1.x)Moved To
League\CommonMark\Util\ConfigurationAwareInterfaceLeague\Config\ConfigurationAwareInterface
League\CommonMark\Util\ConfigurationInterfaceLeague\Config\ConfigurationInterface
League\CommonMark\Util\ConfigurationLeague\Config\Configuration
+ +

Classes/Namespaces Renamed

+ +

Many classes now live in different namespaces, and some have also been renamed. Here’s a quick guide showing their new locations:

+ +

(Note that the base namespace of League\CommonMark has been omitted from this table for brevity.)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Class Namespace/Name (1.x)New Class Namespace/Name (2.0)
ConfigurableEnvironmentInterfaceEnvironment\ConfigurableEnvironmentInterface
EnvironmentAwareInterfaceEnvironment\EnvironmentAwareInterface
EnvironmentEnvironment\Environment
EnvironmentInterfaceEnvironment\EnvironmentInterface
Extension\CommonMarkCoreExtensionExtension\CommonMark\CommonMarkCoreExtension
Delimiter\Processor\EmphasisDelimiterProcessorExtension\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor
Block\Element\BlockQuoteExtension\CommonMark\Node\Block\BlockQuote
Block\Element\FencedCodeExtension\CommonMark\Node\Block\FencedCode
Block\Element\HeadingExtension\CommonMark\Node\Block\Heading
Block\Element\HtmlBlockExtension\CommonMark\Node\Block\HtmlBlock
Block\Element\IndentedCodeExtension\CommonMark\Node\Block\IndentedCode
Block\Element\ListBlockExtension\CommonMark\Node\Block\ListBlock
Block\Element\ListDataExtension\CommonMark\Node\Block\ListData
Block\Element\ListItemExtension\CommonMark\Node\Block\ListItem
Block\Element\ThematicBreakExtension\CommonMark\Node\Block\ThematicBreak
Inline\Element\AbstractWebResourceExtension\CommonMark\Node\Inline\AbstractWebResource
Inline\Element\CodeExtension\CommonMark\Node\Inline\Code
Inline\Element\EmphasisExtension\CommonMark\Node\Inline\Emphasis
Inline\Element\HtmlInlineExtension\CommonMark\Node\Inline\HtmlInline
Inline\Element\ImageExtension\CommonMark\Node\Inline\Image
Inline\Element\LinkExtension\CommonMark\Node\Inline\Link
Inline\Element\StrongExtension\CommonMark\Node\Inline\Strong
Block\Parser\BlockQuoteParserExtension\CommonMark\Parser\Block\BlockQuoteParser
Block\Parser\FencedCodeParserExtension\CommonMark\Parser\Block\FencedCodeParser
Block\Parser\ATXHeadingParser and Block\Parser\SetExtHeadingParserExtension\CommonMark\Parser\Block\HeadingParser
Block\Parser\HtmlBlockParserExtension\CommonMark\Parser\Block\HtmlBlockParser
Block\Parser\IndentedCodeParserExtension\CommonMark\Parser\Block\IndentedCodeParser
Block\Parser\ListParserExtension\CommonMark\Parser\Block\ListParser
Block\Parser\ThematicBreakParserExtension\CommonMark\Parser\Block\ThematicBreakParser
Inline\Parser\AutolinkParserExtension\CommonMark\Parser\Inline\AutolinkParser
Inline\Parser\BacktickParserExtension\CommonMark\Parser\Inline\BacktickParser
Inline\Parser\BangParserExtension\CommonMark\Parser\Inline\BangParser
Inline\Parser\CloseBracketParserExtension\CommonMark\Parser\Inline\CloseBracketParser
Inline\Parser\EntityParserExtension\CommonMark\Parser\Inline\EntityParser
Inline\Parser\EscapableParserExtension\CommonMark\Parser\Inline\EscapableParser
Inline\Parser\HtmlInlineParserExtension\CommonMark\Parser\Inline\HtmlInlineParser
Inline\Parser\OpenBracketParserExtension\CommonMark\Parser\Inline\OpenBracketParser
Block\Renderer\BlockQuoteRendererExtension\CommonMark\Renderer\Block\BlockQuoteRenderer
Block\Renderer\FencedCodeRendererExtension\CommonMark\Renderer\Block\FencedCodeRenderer
Block\Renderer\HeadingRendererExtension\CommonMark\Renderer\Block\HeadingRenderer
Block\Renderer\HtmlBlockRendererExtension\CommonMark\Renderer\Block\HtmlBlockRenderer
Block\Renderer\IndentedCodeRendererExtension\CommonMark\Renderer\Block\IndentedCodeRenderer
Block\Renderer\ListBlockRendererExtension\CommonMark\Renderer\Block\ListBlockRenderer
Block\Renderer\ListItemRendererExtension\CommonMark\Renderer\Block\ListItemRenderer
Block\Renderer\ThematicBreakRendererExtension\CommonMark\Renderer\Block\ThematicBreakRenderer
Inline\Renderer\CodeRendererExtension\CommonMark\Renderer\Inline\CodeRenderer
Inline\Renderer\EmphasisRendererExtension\CommonMark\Renderer\Inline\EmphasisRenderer
Inline\Renderer\HtmlInlineRendererExtension\CommonMark\Renderer\Inline\HtmlInlineRenderer
Inline\Renderer\ImageRendererExtension\CommonMark\Renderer\Inline\ImageRenderer
Inline\Renderer\LinkRendererExtension\CommonMark\Renderer\Inline\LinkRenderer
Inline\Renderer\StrongRendererExtension\CommonMark\Renderer\Inline\StrongRenderer
Extension\SmartPunct\PunctuationParserExtension\SmartPunct\DashParser and Extension\SmartPunct\EllipsesParser
Extension\TableOfContents\TableOfContentsExtension\TableOfContents\Node\TableOfContents
Block\Element\AbstractBlockNode\Block\AbstractBlock
Block\Element\DocumentNode\Block\Document
Block\Element\InlineContainerInterfaceNode\Block\InlineContainerInterface
Block\Element\ParagraphNode\Block\Paragraph
Block\Element\StringContainerInterfaceNode\StringContainerInterface
Inline\Element\AbstractInlineNode\Inline\AbstractInline
Inline\Element\AbstractStringContainerNode\Inline\AbstractStringContainer
Inline\AdjacentTextMergerNode\Inline\AdjacentTextMerger
Inline\Element\NewlineNode\Inline\Newline
Inline\Element\TextNode\Inline\Text
Block\Parser\BlockParserInterfaceParser\Block\BlockContinueParserInterface and Parser\Block\BlockStartParserInterface
Block\Parser\LazyParagraphParserParser\Block\ParagraphParser
CursorParser\Cursor
DocParserParser\MarkdownParser
DocParserInterfaceParser\MarkdownParserInterface
Inline\Parser\InlineParserInterfaceParser\Inline\InlineParserInterface
Inline\Parser\NewlineParserParser\Inline\NewlineParser
InlineParserContextParser\InlineParserContext
InlineParserEngineParser\InlineParserEngine
Block\Renderer\DocumentRendererRenderer\Block\DocumentRenderer
Block\Renderer\ParagraphRendererRenderer\Block\ParagraphRenderer
ElementRendererInterfaceRenderer\ChildNodeRendererInterface
HtmlRendererRenderer\HtmlRenderer
Inline\Renderer\NewlineRendererRenderer\Inline\NewlineRenderer
Inline\Renderer\TextRendererRenderer\Inline\TextRenderer
Block\Renderer\BlockRendererInterface and Inline\Renderer\InlineRendererInterfaceRenderer\NodeRendererInterface
HtmlElementUtil\HtmlElement
+ +

New Block Parsing Approach

+ +

We’ve completely changed how block parsing works in 2.0. In a nutshell, 1.x had parsing responsibilities split between +the parser and the node. But nodes should be “dumb” and not know anything about how they are parsed - they should only +know the bare minimum needed for rendering.

+ +

As a result, 2.x has delegated the parsing responsibilities to two different interfaces:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ResponsibilityOld Method (1.x)New Method (2.0)
Identifying the start of a blockBlockParserInterface::parse()BlockStartParserInterface::tryStart()
Instantiating and configuring the new blockBlockParserInterface::parse()BlockContinueParserInterface::__construct()
Determining if the block acts as a containerAbstractBlock::isContainer()BlockContinueParserInterface::isContainer()
Determining if the block can have lazy continuation linesAbstractBlock::isCode()BlockContinueParserInterface::canHaveLazyContinuationLines()
Determining if the block can contain certain child blocksAbstractBlock::canContain()BlockContinueParserInterface::canContain()
Determining if the block continues on the next lineAbstractBlock::matchesNextLine()BlockContinueParserInterface::tryContinue()
Adding the next line to the blockAbstractBlock::handleRemainingContents()BlockContinueParserInterface::addLine()
Finalizing the block and its contentsAbstractBlock::finalize()BlockContinueParserInterface::closeBlock()
+ +

As a result of making this change, the addBlockParser() method on ConfigurableEnvironmentInterface has changed to addBlockStartParser().

+ +

See the block parsing documentation for more information on this new approach.

+ +

New Inline Parsing Approach

+ +

The getCharacters() method on InlineParserInterface has been replaced with a more-robust getMatchDefinition() method which allows your parser to match against more than just single characters. All custom inline parsers will need to change to this new approach.

+ +

Additionally, when the parse() method is called, the Cursor is no longer proactively advanced past the matching character/start position for you. You’ll need to advance this yourself. However, the InlineParserContext now provides the fully-matched text and its length, allowing you to easily advanceBy() the cursor without having to do an expensive $cursor->match() yourself which is a nice performance optimization.

+ +

See the inline parsing documentation for more information on this new approach.

+ +

Rendering Changes

+ +

This library no longer differentiates between block renderers and inline renderers - everything now uses “node renderers” which allow us to have a unified approach to rendering! As a result, the following changes were made, which you may need to change in your custom extensions:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Method/Interface (1.x)New Method/Interface (2.0)
BlockRendererInterfaceNodeRendererInterface
InlineRendererInterfaceNodeRendererInterface
EnvironmentInterface::getBlockRenderersForClass()EnvironmentInterface::getRenderersForClass()
EnvironmentInterface::getInlineRenderersForClass()EnvironmentInterface::getRenderersForClass()
ConfigurableEnvironmentInterface::addBlockRenderer()ConfigurableEnvironmentInterface::addRenderer()
ConfigurableEnvironmentInterface::addInlineRenderer()ConfigurableEnvironmentInterface::addRenderer()
ElementRendererInterface::renderBlock()ChildNodeRendererInterface::renderNodes()
ElementRendererInterface::renderBlocks()ChildNodeRendererInterface::renderNodes()
ElementRendererInterface::renderInline()ChildNodeRendererInterface::renderNodes()
ElementRendererInterface::renderInlines()ChildNodeRendererInterface::renderNodes()
HtmlRenderer::renderBlock($document)HtmlRenderer::renderDocument()
+ +

Renderers now implement the unified NodeRendererInterface which has a similar (but slightly different) signature from +the old BlockRendererInterface and InlineRendererInterface interfaces:

+ +
/**
+ * @param Node                       $node
+ * @param ChildNodeRendererInterface $childRenderer
+ *
+ * @return HtmlElement|string|null
+ */
+public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The element being rendered is still passed in the first argument, and the object that helps you render children is still +passed in the second argument. Note that blocks are no longer told whether they’re being rendered in a tight list - if you +need to know about this, traverse up the $node AST yourself and check any ListBlock ancestor for tightness.

+ +

AST Node Changes

+ +

The AbstractBlock::$data and AbstractInline::$data arrays were replaced with a Data array-like object on the base Node class.

+ +

Removed Classes

+ +

The following classes have been removed:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Class name in 1.xReplacement / Notes
AbstractStringContainerBlockUse extends AbstractBlock implements StringContainerInterface instead. Note the new method names.
ContextUse MarkdownParserState instead (has different methods but serves a similar purpose)
ContextInterfaceUse MarkdownParserStateInterface instead (has different methods but serves a similar purpose)
ConverterUse MarkdownConverter instead.
ConverterInterfaceUse MarkdownConverterInterface. This interface has the same methods so it should be a drop-in replacement.
UnmatchedBlockCloserNo longer needed 2.x
+ +

Renamed constants

+ +

The following constants have been moved/renamed:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Name/Location (1.x)New Name/Location (2.0)
EnvironmentInterface::HTML_INPUT_ALLOWHtmlFilter::ALLOW
EnvironmentInterface::HTML_INPUT_ESCAPEHtmlFilter::ESCAPE
EnvironmentInterface::HTML_INPUT_STRIPHtmlFilter::STRIP
TableCell::TYPE_HEADTableCell::TYPE_HEADER
TableCell::TYPE_BODYTableCell::TYPE_DATA
+ +

Renamed Methods

+ +

The following methods have been renamed:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassOld Name (1.x)New Name (2.0)
Environment / ConfigurableEnvironmentInterfaceaddBlockParser()addBlockStartParser()
ReferenceMap / ReferenceMapInterfaceaddReference()add()
ReferenceMap / ReferenceMapInterfacegetReference()get()
ReferenceMap / ReferenceMapInterfacelistReferences()getIterator()
+ +

Visibility Changes

+ +

The following properties have had their visibility changed:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyWas (1.x)Is Now (2.0)Notes
TableCell::$alignpublicprivateUse getAlign() and setAlign() instead
TableCell::$typepublicprivateUse getType() and setType() instead
TableSection::$typepublicprivateUse getType() instead
+ +

Configuration Method Changes

+ +

Calling EnvironmentInterface::getConfig() without any parameters is no longer supported.

+ +

Calling ConfigurableEnvironmentInterface::mergeConfig() without any parameters is no longer supported.

+ +

The ConfigurableEnvironmentInterface::setConfig() method has been removed. Use getConfig() instead.

+ +

New approach to the ReferenceParser

+ +

The ReferenceParser class in 1.x worked on complete paragraphs of text. This has been changed in 2.x to work in a more-gradual fashion, where parsing is done on-the-fly as new lines are added. +Whereas you may have previously called parse() on a Cursor once on something containing multiple lines, you should now call parse() on each line of text and then later call getReferences() +to check what has been parsed.

+ +

Html5Entities utility class removed

+ +

Use the Html5EntityDecoder utility class instead.

+ +

bin/commonmark command

+ +

This command was buggy to test and was relatively unpopular, so it has been removed. If you need this type of functionality, consider writing your own script with a Converter/Environment configured exactly how you want it.

+ +

CommonMarkConverter::VERSION constant

+ +

This previously-deprecated constant was removed in 2.0 Use \Composer\InstalledVersions provided by composer-runtime-api instead.

+ +

HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant

+ +

This previously-deprecated constant was removed in 2.0. Use HeadingPermalinkRenderer::DEFAULT_SYMBOL instead.

+ +

ArrayCollection changes

+ +

Several methods were removed from this class - here are the methods along with possible alternatives you can switch to:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Removed Method NameAlternative
add($value)$collection[] = $value
set($key, $value)$collection[$key] = $value
get($key)$collection[$key]
remove($key)unset($collection[$key])
isEmpty()count($collection) === 0
contains($value)in_array($value, $collection->toArray(), true)
indexOf($value)array_search($value, $collection->toArray(), true)
containsKey($key)isset($collection[$key])
replaceWith()(none provided)
removeGaps()(none provided)
+ +

This class is also final now, so don’t extend it.

+ +

final classes

+ +

The following classes are now marked final and cannot be extended:

+ +
    +
  • ArrayCollection
  • +
  • Emphasis
  • +
  • FencedCode
  • +
  • Heading
  • +
  • HtmlBlock
  • +
  • HtmlElement
  • +
  • HtmlInline
  • +
  • IndentedCode
  • +
  • Newline
  • +
  • Strikethrough
  • +
  • Strong
  • +
  • Text
  • +
+ +

Node setter methods return void

+ +

All set*() methods on all Node types now return void (whereas some used to return $this in 1.x) for consistency.

+ +

Unused methods

+ +

The following unused methods have been removed:

+ +
    +
  • Delimiter::setCanClose()
  • +
+ +

Slug Normalizer

+ +

Need to generate unique slugs in your extensions? Use the new Slug Normalizer provided by the Environment.

+ +

Text Normalizers

+ +

The second argument to TextNormalizerInterface::normalize() used to allow any arbitrary object. This was changed to an array so that multiple things can be passed in at once.

+ + + +

The title attribute for Link and Image nodes is now stored using a dedicated property instead of stashing it in $data. Use getTitle() and setTitle() to access the value.

+ +

Node Iteration

+ +

In 1.x, most custom code used $node->walker() to iterate the AST. Although this still exists, consider whether your code could use $node->iterator() instead which can be up to twice as fast!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/upgrading/index.html b/2.0/upgrading/index.html new file mode 100644 index 0000000000..6242ec2e73 --- /dev/null +++ b/2.0/upgrading/index.html @@ -0,0 +1,421 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.6 to 2.0 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 1.6 to 2.0

+ +

Version 2.0 contains lots of changes throughout the library. We’ve split the upgrade guide into three sections to help you better identify the changes that are most relevant to you:

+ +

For Consumers

+ +

The upgrade guide for consumers is relevant for developers who use this library as-is to perform basic conversion of Markdown to HTML. You might enable some extensions or tweak the configuration settings, but you don’t write your own custom parsers or anything like that. This condensed upgrade guide therefore only covers the most obvious changes that might impact your usage of this library.

+ +

For Integrators

+ +

If you develop open-source software that uses this library, read the upgrade guide for integrators. It contains all of the information from the Consumer guide above, but with additional details that may be relevant to you.

+ +

For Developers

+ +

The upgrade guide for developers is aimed at developers who create custom extensions/parsers/renderers and need to know about all of the under-the-hood changes in 2.x. It is the most comprehensive guide, containing all of the information from the two guides above, and even more details about the under-the-hood changes that likely impact your customizations.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/upgrading/integrators/index.html b/2.0/upgrading/integrators/index.html new file mode 100644 index 0000000000..9351be3ed2 --- /dev/null +++ b/2.0/upgrading/integrators/index.html @@ -0,0 +1,1117 @@ + + + + + + + + + + + + + + + + + Upgrading from 1.6 to 2.0 (for integrators) - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

Minimum PHP Version

+ +

The minimum supported PHP version was increased from 7.1 to 7.4.

+ +

CommonMarkConverter and GithubFlavoredMarkdownConverter constructors

+ +

The constructor methods for both CommonMarkConverter and GithubFlavoredMarkdownConverter no longer accept passing in a customized Environment. If you want to customize the extensions used in your converter you should switch to using MarkdownConverter. See the Basic Usage documentation for an example.

+ +
-use League\CommonMark\CommonMarkConverter;
+ use League\CommonMark\Environment\Environment;
+ use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+ use League\CommonMark\Extension\Table\TableExtension;
++use League\CommonMark\MarkdownConverter;
+ 
+ $config = [
+     'html_input' => 'escape',
+     'allow_unsafe_links' => false,
+     'max_nesting_level' => 100,
+ ];
+ 
+-$environment = new Environment();
++$environment = new Environment($config);
+ $environment->addExtension(new CommonMarkCoreExtension());
+ $environment->addExtension(new TableExtension());
+ 
+-$converter = new CommonMarkConverter($config, $environment); // or GithubFlavoredMarkdownConverter
++$converter = new MarkdownConverter($environment);
+ 
+ echo $converter->convertToHtml('Hello World!');
+
+ +

CommonMarkConverter Return Type

+ +

In 1.x, calling convertToHtml() would return a string. In 2.x this changed to return a RenderedContentInterface. To get the resulting HTML, either cast the return value to a string or call ->getContent(). (This new interface extends from Stringable so you can type hint against that instead, if needed.)

+ +
 use League\CommonMark\CommonMarkConverter;
+
+ $converter = new CommonMarkConverter();
+-echo $converter->convertToHtml('# Hello World!');
++echo $converter->convertToHtml('# Hello World!')->getContent();
++// or
++echo (string) $converter->convertToHtml('# Hello World!');
+
+ +

HTML Changes

+ +

Table of Contents items are no longer wrapped with <p> tags:

+ +
 <ul class="table-of-contents">
+     <li>
+-        <p><a href="#level-2-heading">Level 2 Heading</a></p>
++        <a href="#level-2-heading">Level 2 Heading</a>
+     </li>
+     <li>
+-        <p><a href="#level-4-heading">Level 4 Heading</a></p>
++        <a href="#level-4-heading">Level 4 Heading</a>
+     </li>
+     <li>
+-        <p><a href="#level-3-heading">Level 3 Heading</a></p>
++        <a href="#level-3-heading">Level 3 Heading</a>
+     </li>
+ </ul>
+
+ +

See #613 for details.

+ +

Additionally, the HTML (including URL fragments) for Heading Permalinks have changed:

+ +
-<h1><a id="user-content-hello-world" href="#hello-world" name="hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>Hello World!</h1>
++<h1><a id="content-hello-world" href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>Hello World!</h1>
+
+ +

Note that the href now targets the id attribute instead of the name, which is deprecated in HTML 5. Additionally, the default prefix has changed to content. See the Heading Permalink extension documentation for more details on how to configure the prefixes.

+ +

Configuration Option Changes

+ +

Several configuration options now have new names:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Key/PathNew Key/PathNotes
enable_emcommonmark/enable_em 
enable_strongcommonmark/enable_strong 
use_asteriskcommonmark/use_asterisk 
use_underscorecommonmark/use_underscore 
unordered_list_markerscommonmark/unordered_list_markersEmpty arrays no longer allowed
heading_permalink/id_prefix(unchanged)Default is now content
heading_permalink/inner_contentsheading_permalink/symbol 
heading_permalink/slug_normalizerslug_normalizer/instance 
max_nesting_level(unchanged)Only integer values are supported
mentions/*/symbolmentions/*/prefix 
mentions/*/regexmentions/*/patternCannot contain start/end / delimiters
+ +

Method Return Types

+ +

Return types have been added to virtually all class and interface methods. If you implement or extend anything from this library, ensure you also have the proper return types added.

+ +

Configuration Classes Relocated

+ +

The following classes have been moved to the league/config package:

+ + + + + + + + + + + + + + + + + + + + + + +
Old Class Namespace/Name (1.x)Moved To
League\CommonMark\Util\ConfigurationAwareInterfaceLeague\Config\ConfigurationAwareInterface
League\CommonMark\Util\ConfigurationInterfaceLeague\Config\ConfigurationInterface
League\CommonMark\Util\ConfigurationLeague\Config\Configuration
+ +

Classes/Namespaces Renamed

+ +

Many classes now live in different namespaces, and some have also been renamed. Here’s a quick guide showing their new locations:

+ +

(Note that the base namespace of League\CommonMark has been omitted from this table for brevity.)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Class Namespace/Name (1.x)New Class Namespace/Name (2.0)
ConfigurableEnvironmentInterfaceEnvironment\ConfigurableEnvironmentInterface
EnvironmentAwareInterfaceEnvironment\EnvironmentAwareInterface
EnvironmentEnvironment\Environment
EnvironmentInterfaceEnvironment\EnvironmentInterface
Extension\CommonMarkCoreExtensionExtension\CommonMark\CommonMarkCoreExtension
Delimiter\Processor\EmphasisDelimiterProcessorExtension\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor
Block\Element\BlockQuoteExtension\CommonMark\Node\Block\BlockQuote
Block\Element\FencedCodeExtension\CommonMark\Node\Block\FencedCode
Block\Element\HeadingExtension\CommonMark\Node\Block\Heading
Block\Element\HtmlBlockExtension\CommonMark\Node\Block\HtmlBlock
Block\Element\IndentedCodeExtension\CommonMark\Node\Block\IndentedCode
Block\Element\ListBlockExtension\CommonMark\Node\Block\ListBlock
Block\Element\ListDataExtension\CommonMark\Node\Block\ListData
Block\Element\ListItemExtension\CommonMark\Node\Block\ListItem
Block\Element\ThematicBreakExtension\CommonMark\Node\Block\ThematicBreak
Inline\Element\AbstractWebResourceExtension\CommonMark\Node\Inline\AbstractWebResource
Inline\Element\CodeExtension\CommonMark\Node\Inline\Code
Inline\Element\EmphasisExtension\CommonMark\Node\Inline\Emphasis
Inline\Element\HtmlInlineExtension\CommonMark\Node\Inline\HtmlInline
Inline\Element\ImageExtension\CommonMark\Node\Inline\Image
Inline\Element\LinkExtension\CommonMark\Node\Inline\Link
Inline\Element\StrongExtension\CommonMark\Node\Inline\Strong
Block\Parser\BlockQuoteParserExtension\CommonMark\Parser\Block\BlockQuoteParser
Block\Parser\FencedCodeParserExtension\CommonMark\Parser\Block\FencedCodeParser
Block\Parser\ATXHeadingParser and Block\Parser\SetExtHeadingParserExtension\CommonMark\Parser\Block\HeadingParser
Block\Parser\HtmlBlockParserExtension\CommonMark\Parser\Block\HtmlBlockParser
Block\Parser\IndentedCodeParserExtension\CommonMark\Parser\Block\IndentedCodeParser
Block\Parser\ListParserExtension\CommonMark\Parser\Block\ListParser
Block\Parser\ThematicBreakParserExtension\CommonMark\Parser\Block\ThematicBreakParser
Inline\Parser\AutolinkParserExtension\CommonMark\Parser\Inline\AutolinkParser
Inline\Parser\BacktickParserExtension\CommonMark\Parser\Inline\BacktickParser
Inline\Parser\BangParserExtension\CommonMark\Parser\Inline\BangParser
Inline\Parser\CloseBracketParserExtension\CommonMark\Parser\Inline\CloseBracketParser
Inline\Parser\EntityParserExtension\CommonMark\Parser\Inline\EntityParser
Inline\Parser\EscapableParserExtension\CommonMark\Parser\Inline\EscapableParser
Inline\Parser\HtmlInlineParserExtension\CommonMark\Parser\Inline\HtmlInlineParser
Inline\Parser\OpenBracketParserExtension\CommonMark\Parser\Inline\OpenBracketParser
Block\Renderer\BlockQuoteRendererExtension\CommonMark\Renderer\Block\BlockQuoteRenderer
Block\Renderer\FencedCodeRendererExtension\CommonMark\Renderer\Block\FencedCodeRenderer
Block\Renderer\HeadingRendererExtension\CommonMark\Renderer\Block\HeadingRenderer
Block\Renderer\HtmlBlockRendererExtension\CommonMark\Renderer\Block\HtmlBlockRenderer
Block\Renderer\IndentedCodeRendererExtension\CommonMark\Renderer\Block\IndentedCodeRenderer
Block\Renderer\ListBlockRendererExtension\CommonMark\Renderer\Block\ListBlockRenderer
Block\Renderer\ListItemRendererExtension\CommonMark\Renderer\Block\ListItemRenderer
Block\Renderer\ThematicBreakRendererExtension\CommonMark\Renderer\Block\ThematicBreakRenderer
Inline\Renderer\CodeRendererExtension\CommonMark\Renderer\Inline\CodeRenderer
Inline\Renderer\EmphasisRendererExtension\CommonMark\Renderer\Inline\EmphasisRenderer
Inline\Renderer\HtmlInlineRendererExtension\CommonMark\Renderer\Inline\HtmlInlineRenderer
Inline\Renderer\ImageRendererExtension\CommonMark\Renderer\Inline\ImageRenderer
Inline\Renderer\LinkRendererExtension\CommonMark\Renderer\Inline\LinkRenderer
Inline\Renderer\StrongRendererExtension\CommonMark\Renderer\Inline\StrongRenderer
Extension\SmartPunct\PunctuationParserExtension\SmartPunct\DashParser and Extension\SmartPunct\EllipsesParser
Extension\TableOfContents\TableOfContentsExtension\TableOfContents\Node\TableOfContents
Block\Element\AbstractBlockNode\Block\AbstractBlock
Block\Element\DocumentNode\Block\Document
Block\Element\InlineContainerInterfaceNode\Block\InlineContainerInterface
Block\Element\ParagraphNode\Block\Paragraph
Block\Element\StringContainerInterfaceNode\StringContainerInterface
Inline\Element\AbstractInlineNode\Inline\AbstractInline
Inline\Element\AbstractStringContainerNode\Inline\AbstractStringContainer
Inline\AdjacentTextMergerNode\Inline\AdjacentTextMerger
Inline\Element\NewlineNode\Inline\Newline
Inline\Element\TextNode\Inline\Text
Block\Parser\BlockParserInterfaceParser\Block\BlockContinueParserInterface and Parser\Block\BlockStartParserInterface
Block\Parser\LazyParagraphParserParser\Block\ParagraphParser
CursorParser\Cursor
DocParserParser\MarkdownParser
DocParserInterfaceParser\MarkdownParserInterface
Inline\Parser\InlineParserInterfaceParser\Inline\InlineParserInterface
Inline\Parser\NewlineParserParser\Inline\NewlineParser
InlineParserContextParser\InlineParserContext
InlineParserEngineParser\InlineParserEngine
Block\Renderer\DocumentRendererRenderer\Block\DocumentRenderer
Block\Renderer\ParagraphRendererRenderer\Block\ParagraphRenderer
ElementRendererInterfaceRenderer\ChildNodeRendererInterface
HtmlRendererRenderer\HtmlRenderer
Inline\Renderer\NewlineRendererRenderer\Inline\NewlineRenderer
Inline\Renderer\TextRendererRenderer\Inline\TextRenderer
Block\Renderer\BlockRendererInterface and Inline\Renderer\InlineRendererInterfaceRenderer\NodeRendererInterface
HtmlElementUtil\HtmlElement
+ +

Most of these won’t be relevant unless your integration allows people to add/remove extensions, parsers, etc. and you might reference those classes directly. Otherwise, if you simply expose the basic classes to developers, check out the shorter, partial list in the consumer upgrade guide.

+ +

Rendering Changes

+ +

This library no longer differentiates between block renderers and inline renderers - everything now uses “node renderers” which allow us to have a unified approach to rendering! As a result, the following changes were made, which you may need to change in your custom extensions:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Old Method/Interface (1.x)New Method/Interface (2.0)
BlockRendererInterfaceNodeRendererInterface
InlineRendererInterfaceNodeRendererInterface
EnvironmentInterface::getBlockRenderersForClass()EnvironmentInterface::getRenderersForClass()
EnvironmentInterface::getInlineRenderersForClass()EnvironmentInterface::getRenderersForClass()
ConfigurableEnvironmentInterface::addBlockRenderer()ConfigurableEnvironmentInterface::addRenderer()
ConfigurableEnvironmentInterface::addInlineRenderer()ConfigurableEnvironmentInterface::addRenderer()
ElementRendererInterface::renderBlock()ChildNodeRendererInterface::renderNodes()
ElementRendererInterface::renderBlocks()ChildNodeRendererInterface::renderNodes()
ElementRendererInterface::renderInline()ChildNodeRendererInterface::renderNodes()
ElementRendererInterface::renderInlines()ChildNodeRendererInterface::renderNodes()
HtmlRenderer::renderBlock($document)HtmlRenderer::renderDocument()
+ +

Renderers now implement the unified NodeRendererInterface which has a similar (but slightly different) signature from +the old BlockRendererInterface and InlineRendererInterface interfaces:

+ +
/**
+ * @param Node                       $node
+ * @param ChildNodeRendererInterface $childRenderer
+ *
+ * @return HtmlElement|string|null
+ */
+public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The element being rendered is still passed in the first argument, and the object that helps you render children is still +passed in the second argument. Note that blocks are no longer told whether they’re being rendered in a tight list - if you +need to know about this, traverse up the $node AST yourself and check any ListBlock ancestor for tightness.

+ +

Removed Classes

+ +

The following classes have been removed:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Class name in 1.xReplacement / Notes
AbstractStringContainerBlockUse extends AbstractBlock implements StringContainerInterface instead. Note the new method names.
ContextUse MarkdownParserState instead (has different methods but serves a similar purpose)
ContextInterfaceUse MarkdownParserStateInterface instead (has different methods but serves a similar purpose)
ConverterUse MarkdownConverter instead.
ConverterInterfaceUse MarkdownConverterInterface. This interface has the same methods so it should be a drop-in replacement.
UnmatchedBlockCloserNo longer needed 2.x
+ +

Renamed constants

+ +

The following constants have been moved/renamed:

+ + + + + + + + + + + + + + + + + + + + + + +
Old Name/Location (1.x)New Name/Location (2.0)
EnvironmentInterface::HTML_INPUT_ALLOWHtmlFilter::ALLOW
EnvironmentInterface::HTML_INPUT_ESCAPEHtmlFilter::ESCAPE
EnvironmentInterface::HTML_INPUT_STRIPHtmlFilter::STRIP
+ +

Renamed Methods

+ +

The following methods have been renamed:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClassOld Name (1.x)New Name (2.0)
Environment / ConfigurableEnvironmentInterfaceaddBlockParser()addBlockStartParser()
ReferenceMap / ReferenceMapInterfaceaddReference()add()
ReferenceMap / ReferenceMapInterfacegetReference()get()
ReferenceMap / ReferenceMapInterfacelistReferences()getIterator()
+ +

Configuration Method Changes

+ +

Calling EnvironmentInterface::getConfig() without any parameters is no longer supported.

+ +

Calling ConfigurableEnvironmentInterface::mergeConfig() without any parameters is no longer supported.

+ +

The ConfigurableEnvironmentInterface::setConfig() method has been removed. Use getConfig() instead.

+ +

bin/commonmark command

+ +

This command was buggy to test and was relatively unpopular, so it has been removed. If you need this type of functionality, consider writing your own script with a Converter/Environment configured exactly how you want it.

+ +

CommonMarkConverter::VERSION constant

+ +

This previously-deprecated constant was removed in 2.0 Use \Composer\InstalledVersions provided by composer-runtime-api instead.

+ +

HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant

+ +

This previously-deprecated constant was removed in 2.0. Use HeadingPermalinkRenderer::DEFAULT_SYMBOL instead.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.0/xml/index.html b/2.0/xml/index.html new file mode 100644 index 0000000000..b4227b77fe --- /dev/null +++ b/2.0/xml/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + XML Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.0. Please consider upgrading your code to the latest stable version

+ + +

XML Rendering

+ +

Version 2.0 introduced the ability to render Markdown Document objects in XML. This is particularly useful for debugging custom extensions.

+ +

To convert Markdown to XML, you would instantiate an Environment, parse the Markdown into an AST, and render it using the new XmlRenderer:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Xml\XmlRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$renderer = new XmlRenderer($environment);
+
+$document = $parser->parse('# **Hello** World!');
+
+echo $renderer->renderDocument($document);
+
+ +

This will display XML output like this:

+ +
<?xml version="1.0" encoding="UTF-8"?>
+<document xmlns="http://commonmark.org/xml/1.0">
+    <heading level="1">
+        <strong>
+            <text>Hello</text>
+        </strong>
+        <text> World!</text>
+    </heading>
+</document>
+
+ +

Return Value

+ +

Like with CommonMarkConverter::convertToHtml(), the renderDocument() actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final XML output.

+ +

Customizing the XML Output

+ +

See the rendering documentation for information on customizing the XML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/basic-usage/index.html b/2.1/basic-usage/index.html new file mode 100644 index 0000000000..250f22797f --- /dev/null +++ b/2.1/basic-usage/index.html @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Using Extensions

+ +

The CommonMarkConverter and GithubFlavoredMarkdownConverter shown above automatically configure the environment for you, but if you want to use additional extensions you’ll need to avoid those classes and use the generic MarkdownConverter class instead to customize the environment with whatever extensions you wish to use:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+$environment->addExtension(new InlinesOnlyExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+// <p><strong>Hello World!</strong></p>
+
+ +

Configuration

+ +

If you’re using the CommonMarkConverter or GithubFlavoredMarkdownConverter class you can pass configuration options directly into their constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

See the configuration section for more information on the available configuration options.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ +

Return Value

+ +

The convertToHtml() method actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final HTML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/changelog/index.html b/2.1/changelog/index.html new file mode 100644 index 0000000000..feaf30ab50 --- /dev/null +++ b/2.1/changelog/index.html @@ -0,0 +1,829 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 2.x releases are shown below. See the full list of releases for the complete changelog.

+ +

2.5.3 - 2024-08-16

+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.1, including: +
      +
    • Remove source, add search to list of recognized block tags
    • +
    +
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.2…2.5.3

+ +

2.5.2 - 2024-08-14

+ +

Changed

+ +
    +
  • Boolean attributes now require an explicit true value (#1040)
  • +
+ +

Fixed

+ +
    +
  • Fixed regression where text could be misinterpreted as an attribute (#1040)
  • +
+ +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.1…2.5.2

+ +

2.5.1 - 2024-07-24

+ +

Fixed

+ +
    +
  • Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
  • +
  • Fixed incorrect Table start line numbers (#1037)
  • +
+ +

New Contributors

+
    +
  • @jasonvarga made their first contribution in https://github.com/thephpleague/commonmark/pull/1035
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.0…2.5.1

+ +

2.5.0 - 2024-07-22

+ +

Added

+ +
    +
  • The AttributesExtension now supports attributes without values (#985, #986)
  • +
  • The AutolinkExtension exposes two new configuration options to override the default behavior (#969, #987): +
      +
    • autolink/allowed_protocols - an array of protocols to allow autolinking for
    • +
    • autolink/default_protocol - the default protocol to use when none is specified
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.0, including: +
      +
    • Allow closing fence to be followed by tabs
    • +
    • Remove restrictive limitation on inline comments
    • +
    • Unicode symbols now treated like punctuation (for purposes of flankingness)
    • +
    • Trailing tabs on the last line of indented code blocks will be excluded
    • +
    • Improved HTML comment matching
    • +
    +
  • +
  • Paragraphs only containing link reference definitions will be kept in the AST until the Document is finalized +
      +
    • (These were previously removed immediately after parsing the Paragraph)
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed list tightness not being determined properly in some edge cases
  • +
  • Fixed incorrect ending line numbers for several block types in various scenarios
  • +
  • Fixed lowercase inline HTML declarations not being accepted
  • +
+ +

New Contributors

+
    +
  • @svenluijten made their first contribution in https://github.com/thephpleague/commonmark/pull/986
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.4…2.5.0

+ +

2.4.4 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.3…2.4.4

+ +

2.4.3 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
  • +
  • Fixed UrlAutolinkParser incorrectly parsing text containing www anywhere before an autolink (#1025)
  • +
+ +

New Contributors

+
    +
  • @nfreader made their first contribution in https://github.com/thephpleague/commonmark/pull/1016
  • +
  • @macbookandrew made their first contribution in https://github.com/thephpleague/commonmark/pull/1025
  • +
  • @xavierlacot made their first contribution in https://github.com/thephpleague/commonmark/pull/1013
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.2…2.4.3

+ +

2.4.2 - 2024-02-02

+ +

Fixed

+ +
    +
  • Fixed declaration parser being too strict
  • +
  • FencedCodeRenderer: don’t add language- to class if already prefixed
  • +
+ +

New Contributors

+
    +
  • @sergiy-petrov made their first contribution in https://github.com/thephpleague/commonmark/pull/997
  • +
  • @clustermin made their first contribution in https://github.com/thephpleague/commonmark/pull/988
  • +
  • @peter279k made their first contribution in https://github.com/thephpleague/commonmark/pull/996
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.1…2.4.2

+ +

2.4.1 - 2023-08-30

+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not fully disabling the rel attribute when configured to do so (#992)
  • +
+ +

2.4.0 - 2023-03-24

+ +

See the upgrading guide for more information about the exception-related changes

+ +

Added

+ +
    +
  • Added generic CommonMarkException marker interface for all exceptions thrown by the library
  • +
  • Added several new specific exception types implementing that marker interface: +
      +
    • AlreadyInitializedException
    • +
    • InvalidArgumentException
    • +
    • IOException
    • +
    • LogicException
    • +
    • MissingDependencyException
    • +
    • NoMatchingRendererException
    • +
    • ParserLogicException
    • +
    +
  • +
  • Added more configuration options to the Heading Permalinks extension (#939): +
      +
    • heading_permalink/apply_id_to_heading - When true, the id attribute will be applied to the heading element itself instead of the <a> tag
    • +
    • heading_permalink/heading_class - class to apply to the heading element
    • +
    • heading_permalink/insert - now accepts none to prevent the creation of the <a> link
    • +
    +
  • +
  • Added new table/alignment_attributes configuration option to control how table cell alignment is rendered (#959)
  • +
+ +

Changed

+ +
    +
  • Change several thrown exceptions from RuntimeException to LogicException (or something extending it), including: +
      +
    • CallbackGenerators that fail to set a URL or return an expected value
    • +
    • MarkdownParser when deactivating the last block parser or attempting to get an active block parser when they’ve all been closed
    • +
    • Adding items to an already-initialized Environment
    • +
    • Rendering a Node when no renderer has been registered for it
    • +
    +
  • +
  • HeadingPermalinkProcessor now throws InvalidConfigurationException instead of RuntimeException when invalid config values are given.
  • +
  • HtmlElement::setAttribute() no longer requires the second parameter for boolean attributes
  • +
  • Several small micro-optimizations
  • +
  • Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
  • +
+ +

Fixed

+ +
    +
  • Fixed inaccurate @throws docblocks throughout the codebase, including ConverterInterface, MarkdownConverter, and MarkdownConverterInterface. +
      +
    • These previously suggested that only \RuntimeExceptions were thrown, which was inaccurate as \LogicExceptions were also possible.
    • +
    +
  • +
+ +

2.3.9 - 2023-02-15

+ +

Fixed

+ +
    +
  • Fixed autolink extension not detecting some URIs with underscores (#956)
  • +
+ +

2.3.8 - 2022-12-10

+ +

Fixed

+ +
    +
  • Fixed parsing issues when mb_internal_encoding() is set to something other than UTF-8 (#951)
  • +
+ +

2.3.7 - 2022-11-17

+ +

Fixed

+ +
    +
  • Fixed TaskListItemMarkerRenderer not including HTML attributes set on the node by other extensions (#947)
  • +
+ +

2.3.6 - 2022-10-30

+ +

Fixed

+ +
    +
  • Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a .) (#943)
  • +
+ +

2.3.5 - 2022-07-29

+ +

Fixed

+ +
    +
  • Fixed error using InlineParserEngine when no inline parsers are registered in the Environment (#908)
  • +
+ +

2.3.4 - 2022-07-17

+ +

Changed

+ +
    +
  • Made a number of small tweaks to the embed extension’s parsing behavior to fix #898: +
      +
    • Changed EmbedStartParser to always capture embed-like lines in container blocks, regardless of parent block type
    • +
    • Changed EmbedProcessor to also remove Embed blocks that aren’t direct children of the Document
    • +
    • Increased the priority of EmbedProcessor to 1010
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed EmbedExtension not parsing embeds following a list block (#898)
  • +
+ +

2.3.3 - 2022-06-07

+ +

Fixed

+ +
    +
  • Fixed DomainFilteringAdapter not reindexing the embed list (#884, #885)
  • +
+ +

2.3.2 - 2022-06-03

+ +

Fixed

+ +
    +
  • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
  • +
+ +

2.2.5 - 2022-06-03

+ +

Fixed

+ +
    +
  • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
  • +
+ +

2.3.1 - 2022-05-14

+ +

Fixed

+ +
    +
  • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
  • +
+ +

2.2.4 - 2022-05-14

+ +

Fixed

+ +
    +
  • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
  • +
+ +

2.3.0 - 2022-04-07

+ +

Added

+ +
    +
  • Added new EmbedExtension (#805)
  • +
  • Added DocumentRendererInterface as a replacement for the now-deprecated MarkdownRendererInterface
  • +
+ +

Deprecated

+ +
    +
  • Deprecated MarkdownRendererInterface; use DocumentRendererInterface instead
  • +
+ +

2.2.3 - 2022-02-26

+ +

Fixed

+ +
    +
  • Fixed front matter parsing with Windows line endings (#821)
  • +
+ +

2.1.3 - 2022-02-26

+ +

Fixed

+ +
    +
  • Fixed front matter parsing with Windows line endings (#821)
  • +
+ +

2.2.2 - 2022-02-13

+ +

Fixed

+ +
    +
  • Fixed double-escaping of image alt text (#806, #810)
  • +
  • Fixed Psalm typehints for event class names
  • +
+ +

2.1.2 - 2022-02-13

+ +

Fixed

+ +
    +
  • Fixed double-escaping of image alt text (#806, #810)
  • +
  • Fixed Psalm typehints for event class names
  • +
+ +

2.2.1 - 2022-01-25

+ +

Fixed

+ +
    +
  • Fixed symfony/deprecation-contracts constraint
  • +
+ +

Removed

+ +
    +
  • Removed deprecation trigger from MarkdownConverterInterface to reduce noise
  • +
+ +

2.2.0 - 2022-01-22

+ +

Added

+ +
    +
  • Added new ConverterInterface
  • +
  • Added new MarkdownToXmlConverter class
  • +
  • Added new HtmlDecorator class which can wrap existing renderers with additional HTML tags
  • +
  • Added new table/wrap config to apply an optional wrapping/container element around a table (#780)
  • +
+ +

Changed

+ +
    +
  • HtmlElement contents can now consist of any Stringable, not just HtmlElement and string
  • +
+ +

Deprecated

+ +
    +
  • Deprecated MarkdownConverterInterface and its convertToHtml() method; use ConverterInterface and convert() instead
  • +
+ +

2.1.1 - 2022-01-02

+ +

Added

+ +
    +
  • Added missing return type to Environment::dispatch() to fix deprecation warning (#778)
  • +
+ +

2.1.0 - 2021-12-05

+ +

Added

+ +
    +
  • Added support for ext-yaml in FrontMatterExtension (#715)
  • +
  • Added support for symfony/yaml v6.0 in FrontMatterExtension (#739)
  • +
  • Added new heading_permalink/aria_hidden config option (#741)
  • +
+ +

Fixed

+ +
    +
  • Fixed PHP 8.1 deprecation warning (#759, #762)
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/configuration/index.html b/2.1/configuration/index.html new file mode 100644 index 0000000000..755bd74ea3 --- /dev/null +++ b/2.1/configuration/index.html @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

Many aspects of this library’s behavior can be tweaked using configuration options.

+ +

You can provide an array of configuration options to the Environment or converter classes when creating them:

+ +
$config = [
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'commonmark' => [
+        'enable_em' => true,
+        'enable_strong' => true,
+        'use_asterisk' => true,
+        'use_underscore' => true,
+        'unordered_list_markers' => ['-', '*', '+'],
+    ],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => PHP_INT_MAX,
+    'slug_normalizer' => [
+        'max_length' => 255,
+    ],
+];
+
+ +

If you’re using the basic CommonMarkConverter or GithubFlavoredMarkdown classes, simply pass the configuration array into the constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

Here’s a list of the core configuration options available:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: PHP_INT_MAX). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested.
  • +
  • slug_normalizer - Array of options for configuring how URL-safe slugs are created; see the slug normalizer docs for more details +
      +
    • instance - An alternative normalizer to use (defaults to the included SlugNormalizer)
    • +
    • max_length - Limits the size of generated slugs (defaults to 255 characters)
    • +
    • unique - Controls whether slugs should be unique per 'document' (default) or per 'environment'; can be disabled with false
    • +
    +
  • +
+ +

Additional configuration options are available for most of the available extensions - refer to their individual documentation for more details. For example, the CommonMark core extension offers these additional options:

+ +
    +
  • commonmark - Array of options for configuring the CommonMark core extension: +
      +
    • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
    • +
    • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
    • +
    • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
    • +
    • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
    • +
    • unordered_list_markers - Array of characters that can be used to indicate a bulleted list (default: ["-", "*", "+"])
    • +
    +
  • +
+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/abstract-syntax-tree/index.html b/2.1/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..de78f7619e --- /dev/null +++ b/2.1/customization/abstract-syntax-tree/index.html @@ -0,0 +1,702 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the MarkdownParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent see the (Event Dispatcher documentation)
  • +
+ +

Visualization

+ +

Even with an interactive debugger it can be tricky to view an entire tree at once. Consider using the XmlRenderer to provide a simple text-based representation of the AST for debugging purposes.

+ +

Node Traversal

+ +

There are four different ways to traverse/iterate the Nodes within the AST:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodProsCons
Manual TraversalBest way to access/check direct relatives of nodesNot useful for iteration
Iterating the TreeFast and efficientPossible unexpected behavior when adding/removing sibling nodes while iterating
Walking the TreeFull control over iterationUp to twice as slow as iteration; adding/removing nodes while iterating can lead to weird behaviors
Querying NodesEasier to write and understand; no weird behaviorsNot memory efficient
+ +

Each is described in more detail below

+ +

Manual Traversal

+ +

The following methods can be used to manually traverse from one Node to any of its direct relatives:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

This is best suited for situations when you need to know information about those relatives.

+ +

Iterating the Tree

+ +

If you’d like to iterate through all the nodes, use the iterator() method to obtain an iterator that will loop through each node in the tree (using pre-order traversal):

+ +
foreach ($document->iterator() as $node) {
+    echo 'Current node: ' . get_class($node) . "\n";
+}
+
+ +

Given an AST like this (XML representation):

+ +
<document>
+  <heading level="1">
+    <text>Hello World!</text>
+  </heading>
+  <paragraph>
+    <text>This is an example of </text>
+    <strong>
+      <text>CommonMark</text>
+    </strong>
+    <text>.</text>
+  </paragraph>
+</document>
+
+ +

The code above will output:

+ +
Current node: League\CommonMark\Node\Block\Document
+Current node: League\CommonMark\Extension\CommonMark\Node\Block\Heading
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Block\Paragraph
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Extension\CommonMark\Node\Inline\Strong
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Inline\Text
+
+ +

This iterator doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes. It’s also very CPU and memory-efficient.

+ +

Be careful when modifying nodes while iterating the tree as some of those changes may affect the current iteration process, especially for sibling nodes that come after the current one. For example, if you remove the current node’s next() sibling, the next loop of that iteration will still include the removed sibling even though it was successfully removed from the AST. Similarly, any new siblings that are added won’t be visited on the next loop.

+ +

Walking the Tree

+ +

If you’d like to walk through all the nodes, visiting each one as you enter and leave it, use the walker() method to obtain an instance of NodeWalker. This also uses pre-order traversal but emitting NodeWalkerEvents along the way:

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'Now ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

Using the same example AST in the previous section, this code will output:

+ +
Now entering a League\CommonMark\Node\Block\Document node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Block\Paragraph node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Node\Block\Paragraph node
+Now leaving a League\CommonMark\Node\Block\Document node
+
+ +

This approach offers many of the same benefits as the simple iteration shown in the previous section such as memory efficiency and no recursion. The key differences come from how you enter and leave nodes:

+ +
    +
  1. Iteration can potentially take twice as long - not ideal for performance
  2. +
  3. Provides you with more control over exactly when an action is taken on a node which is sometimes needed for certain AST manipulations
  4. +
  5. Also provides a resumeAt() method to override where it should iterate next
  6. +
+ +

But like with the iterator, be careful when adding/removing nodes while walking the tree, as there are even more subtle cases where the walker could even lose track of where it was, which may result in some nodes being visited multiple times or not at all.

+ +

Querying Nodes

+ +

If you’re trying to locate certain nodes to perform actions on them, querying the nodes from the AST might be easier to implement. This can be done with the Query class:

+ +
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Node\Block\Paragraph;
+use League\CommonMark\Node\Query;
+
+// Find all paragraphs and blockquotes that contain links
+$matchingNodes = (new Query())
+    ->where(Query::type(Paragraph::class))
+    ->orWhere(Query::type(BlockQuote::class))
+    ->andWhere(Query::hasChild(Query::type(Link::class)))
+    ->findAll($document);
+
+foreach ($matchingNodes as $node) {
+    // TODO: Do something with them
+}
+
+ +

Each condition passed into where(), orWhere(), or andWhere() must be a callable “filter” that accepts a Node and returns true or false. We provide several methods that can help create these filters for you:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Query::type(string $class)Creates a filter that matches nodes with the given class name
Query::hasChild()Creates a filter that matches nodes which contain at least one child
Query::hasChild(callable $condition)Creates a filter that matches nodes which contain at least one child that matches the inner $condition
Query::hasParent()Creates a filter that matches nodes which have a parent
Query::hasParent(callable $condition)Creates a filter that matches nodes which have a parent that matches the inner $condition
+ +

You can of course create your own custom filters/conditions using an anonymous function or by implementing ExpressionInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Node\Query;
+use League\CommonMark\Node\Query\ExpressionInterface;
+
+class ChildCountGreaterThan implements ExpressionInterface
+{
+    private $count;
+
+    public function __construct(int $count)
+    {
+        $this->count = $count;
+    }
+
+    public function __invoke(Node $node) : bool{
+        return count($node->children()) > $this->count;
+    }
+}
+
+$query = (new Query())
+    ->where(function (Node $node): bool { return $node->data->has('attributes/class'); })
+    ->andWhere(new ChildCountGreaterThan(3));
+
+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ +

Data Storage

+ +

Each Node has a property called data which is a Data (array-like) object. This can be used to store any arbitrary data you’d like on the node:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text1 = new Text('Hello, world!');
+$text1->data->set('language', 'English');
+$text1->data->set('is_good_translation', true);
+
+$text2 = new Text('Bonjour monde!');
+$text2->data->set('language', 'French');
+$text2->data->set('is_good_translation', false);
+
+foreach ([$text1, $text2] as $text) {
+    if ($text->data->get('is_good_translation')) {
+        sprintf('In %s we would say: "%s"', $text->data->get('language'), $text->getLiteral());
+    } else {
+        sprintf('I think they would say "%s" in %s, but I\'m not sure.', $text->getLiteral(), $text->data->get('language'));
+    }
+}
+
+ +

You can also access deeply-nested paths using / or . as delimiters:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text = new Text('Hello, world!');
+$text->data->set('info', ['language' => 'English', 'is_good_translation' => true]);
+
+var_dump($text->data->get('info/language'));
+var_dump($text->data->get('info.is_good_translation'));
+
+$text->data->set('info/is_example', true);
+
+ +

HTML Attributes

+ +

The data property comes pre-instantiated with a single data element called attributes which is used to store any HTML attributes that need to be rendered. For example:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+$link = new Link('https://twitter.com/colinodell', '@colinodell');
+$link->data->append('attributes/class', 'social-link');
+$link->data->append('attributes/class', 'twitter');
+$link->data->set('attributes/target', '_blank');
+$link->data->set('attributes/rel', 'noopener');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/block-parsing/index.html b/2.1/customization/block-parsing/index.html new file mode 100644 index 0000000000..004759b26b --- /dev/null +++ b/2.1/customization/block-parsing/index.html @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

At a high level, block parsing is a two-step process:

+ +
    +
  1. Using a BlockStartParserInterface to identify if/where a block start exists on the given line
  2. +
  3. Using a BlockContinueParserInterface to perform additional processing of the identified block
  4. +
+ +

So to implement a custom block parser you will actually need to implement both of these classes.

+ +

BlockStartParserInterface

+ +

Instances of this interface have a single tryStart() method:

+ +
/**
+ * Check whether we should handle the block at the current position
+ *
+ * @param Cursor                       $cursor
+ * @param MarkdownParserStateInterface $parserState
+ *
+ * @return BlockStart|null
+ */
+public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart;
+
+ +

Given a Cursor at the current position, plus some extra information about the state of the parser, this method is responsible for determining whether a particular type of block seems to exist at the given position. You don’t actually parse the block here - that’s the job of a BlockContinueParserInterface. Your only job here is to return whether or not a particular type of block does exist here, and if so which block parser should parse it.

+ +

If you find that you cannot parse the given block, you should return BlockStart::none(); from this function.

+ +

However, if the Markdown at the current position does indeed seem to be the type of block you’re looking for, you should return a BlockStart instance using the following static constructor pattern:

+ +
use League\CommonMark\Parser\Block\BlockStart;
+
+return BlockStart::of(new MyCustomParser())->at($cursor);
+
+ +

Unlike in 1.x, the Cursor state is no longer shared between parsers. You must therefore explicitly provide the BlockStart object with a copy of your cursor at the correct, post-parsing position.

+ +

NOTE: If your custom block starts with a letter character you’ll need to add your parser to the environment with a priority of 250 or higher. This is due to a performance optimization where such lines are usually skipped.

+ +

BlockContinueParserInterface

+ +

The previous interface only helps the engine identify where a block starts. Additional information about the block, as well as the ability to parse additional lines of input, is all handled by the BlockContinueParserInterface.

+ +

This interface has several methods, so it’s usually easier to extend from AbstractBlockContinueParser instead, which sets most of the methods to use typical defaults you can override as needed.

+ +

getBlock()

+ +
public function getBlock(): AbstractBlock;
+
+ +

Each instance of a BlockContinueParserInterface is associated with a new block that is being parsed. This method here returns that block.

+ +

isContainer()

+ +
public function isContainer(): bool;
+
+ +

This method returns whether or not the block is a “container” capable of containing other blocks as children.

+ +

canContain()

+ +
public function canContain(AbstractBlock $childBlock): bool;
+
+ +

This method returns whether the current block being parsed can contain the given child block.

+ +

canHaveLazyContinuationLines()

+ +
public function canHaveLazyContinuationLines(): bool;
+
+ +

This method returns whether or not this parser should also receive subsequent lines of Markdown input. This is primarily used when a block can span multiple lines, like code blocks do.

+ +

addLine()

+ +
public function addLine(string $line): void;
+
+ +

If canHaveLazyContinuationLines() returned true, this method will be called with the additional lines of content.

+ +

tryContinue()

+ +
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue;
+
+ +

This method allows you to try and parse an additional line of Markdown.

+ +

closeBlock()

+ +
public function closeBlock(): void;
+
+ +

This method is called when the block is done being parsed. Any final adjustments to the block should be made at this time.

+ +

parseInlines()

+ +
public function parseInlines(InlineParserEngineInterface $inlineParser): void;
+
+ +

This method is called when the engine is ready to parse any inline child elements.

+ +

Note: For performance reasons, this method is not part of BlockContinueParserInterface. If your block may contain inlines, you should make sure that your “continue parser” also implements BlockContinueParserWithInlinesInterface.

+ +

Tips

+ +

Here are some additional tips to consider when writing your own custom parsers:

+ +

Combining both into one file

+ +

Although parsing requires two classes, you can use the anonymous class feature of PHP to combine both into a single file! Here’s an example:

+ +
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
+use League\CommonMark\Parser\Block\BlockStartParserInterface;
+
+final class MyCustomBlockParser extends AbstractBlockContinueParser
+{
+    // TODO: implement your continuation parsing methods here
+
+    public static function createBlockStartParser(): BlockStartParserInterface
+    {
+        return new class implements BlockStartParserInterface
+        {
+            // TODO: implement the tryStart() method here
+        };
+    }
+}
+
+
+ +

Performance

+ +

The BlockStartParserInterface::tryStart() and BlockContinueParserInterface::tryContinue() methods may be called hundreds or thousands of times during execution. For best performance, have your methods return as early as possible, and make sure your code is highly optimized.

+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

If your block contains literal strings/text within the block (and not as part of a child block), you should have your custom block type also implement StringContainerInterface.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/configuration/index.html b/2.1/customization/configuration/index.html new file mode 100644 index 0000000000..e298f88bb3 --- /dev/null +++ b/2.1/customization/configuration/index.html @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Configuration Schemas and Values

+ +

Version 2.0 introduced a new robust system for defining configuration schemas and accessing them within custom extensions.

+ +

Configuration Schemas

+ +

Unlike in 1.x, all configuration options must have a defined schema. This defines which options are available, what types of values they accept, whether any are required, and any default values you wish to define if the user doesn’t provide any.

+ +

These custom options can be defined from within your custom extension by implementing the ConfigurableExtensionInterface:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+use Nette\Schema\Expect;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        $builder->addSchema('my_extension', Expect::structure([
+            'enable_some_feature' => Expect::bool()->default(true),
+            'html_class' => Expect::string()->default('my-custom-extension'),
+            'align' => Expect::anyOf('left', 'center', 'right')->default('left'),
+            'favorite_number' => Expect::int()->min(1)->max(100)->default(42),
+        ]));
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        // TODO: Implement register() method
+    }
+}
+
+ +

See the league/config documentation for more examples of how to define custom configuration schemas.

+ +

Note that you only need to implement ConfigurableExtensionInterface if you plan to define new configuration options - you don’t need this if you’re only reading existing options.

+ +

Reading Configuration Values

+ +

Okay, so your extension has defined the different options that are available, but now you want to start using them within your custom extension. There are a few ways you can access the values:

+ +

During Extension Registration

+ +

Perhaps your extension needs to decide whether/how to register certain parsers/renderers/etc based on the user-provided configuration values - in that case, you can read the value from the $environment - for example:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        // (see code example above)
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        if ($environment->getConfiguration()->get('my_extension/enable_some_feature')) {
+            $environment->addBlockStartParser(new MyCustomParser());
+            $environment->addRenderer(MyCustomBlockType::class, new MyCustomRenderer());
+        }
+    }
+}
+
+ +

Within Parsers/Renderers/Listeners

+ +

Perhaps you want to reference those configuration values from within a custom parser, renderer, event listener, or something else. This can easily by done by having that class also implement ConfigurationAwareInterface. This interface signals to the Environment that your class needs a copy of the final configuration so it can read it later:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\Config\ConfigurationAwareInterface;
+use League\Config\ConfigurationInterface;
+
+final class MyCustomRenderer implements NodeRendererInterface, ConfigurationAwareInterface
+{
+    /**
+     * @var ConfigurationInterface
+     */
+    private $config;
+
+    public function setConfiguration(ConfigurationInterface $configuration): void
+    {
+        $this->config = $configuration;
+    }
+
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return 'My favorite number is ' . $this->config->get('my_extension/favorite_number');
+    }
+}
+
+ +

You can access any configuration value from here, not just the ones you might have defined yourself.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/cursor/index.html b/2.1/customization/cursor/index.html new file mode 100644 index 0000000000..86b8d73d95 --- /dev/null +++ b/2.1/customization/cursor/index.html @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Parser\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter(int $index)Returns the character at the given absolute position
getCurrentCharacter()Returns the character at the current position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/delimiter-processing/index.html b/2.1/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..b0f338693f --- /dev/null +++ b/2.1/customization/delimiter-processing/index.html @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void;
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
+
+// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
use League\CommonMark\Delimiter\Delimiter;
+use League\CommonMark\Node\Inline\Text;
+
+$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/environment/index.html b/2.1/customization/environment/index.html new file mode 100644 index 0000000000..9dd38456ae --- /dev/null +++ b/2.1/customization/environment/index.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all extensions, parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

An empty Environment can be obtained like this:

+ +
use League\CommonMark\Environment\Environment;
+
+$config = [];
+$environment = new Environment($config);
+
+ +

You can customize the Environment using any of the methods below (from the EnvironmentBuilderInterface interface).

+ +

Once your Environment is configured with whatever configuration and extensions you want, you can instantiate a MarkdownConverter and start converting MD to HTML:

+ +
use League\CommonMark\MarkdownConverter;
+
+// Using $environment from the previous code sample
+$converter = new MarkdownConverter($environment);
+
+echo $converter->convertToHtml('# Hello World!');
+
+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. For example, if you want core CommonMark functionality plus footnote support:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+
+$config = [];
+$environment = new Environment($config);
+
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new FootnoteExtension());
+
+ +

addBlockStartParser()

+ +
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockStartParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addRenderer()

+ +
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0);
+
+ +

Registers a NodeRendererInterface to handle a specific type of AST node ($nodeClass) with the given priority (a higher number will be executed earlier).

+ +

See Rendering for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ +

Accessing the Environment and Configuration within parsers/renderers/etc

+ +

If your custom parser/renderer/listener/etc. implements either EnvironmentAwareInterface or ConfigurationAwareInterface we’ll automatically inject the environment or configuration into them once the environment has been fully initialized. This will provide your code with access to the finalized information it may need.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/event-dispatcher/index.html b/2.1/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..e875ac6f4e --- /dev/null +++ b/2.1/customization/event-dispatcher/index.html @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic, PSR-14-compliant event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code.

+ +

Event Class

+ +

Any PSR-14 compliant event can be used, though we also provide an AbstractEvent class you can use to easily create your own events:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - the event object to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

League\CommonMark\Event\DocumentPreRenderEvent

+ +

This event is dispatched by the renderer just before rendering begins. Like with DocumentParsedEvent, this offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering, but with the added knowledge of which format is being rendered to (e.g. html).

+ +

League\CommonMark\Event\DocumentRenderedEvent

+ +

This event is dispatched once the rendering step has been completed, just before the output is returned. The final output can be adjusted at this point or additional metadata can be attached to the return object.

+ +

Bring Your Own PSR-14 Event Dispatcher

+ +

Although this library provides PSR-14 compliant event dispatching out-of-the-box, you may want to use your own PSR-14 event dispatcher instead. This is possible as long as that third-party library both:

+ +
    +
  1. Implements the PSR-14 EventDispatcherInterface; and,
  2. +
  3. Allows you to register additional ListenerProviderInterface instances with that dispatcher library
  4. +
+ +

Not all libraries support this so please check carefully! Assuming yours does, delegating all the event behavior to that library can be done with two steps:

+ +

First, call the setEventDispatcher() method on the Environment to register that other implementation. With that done, any calls to Environment::dispatch() will be passed through to that other dispatcher. But we still need to let that dispatcher know about the events registered by CommonMark extensions, otherwise nothing will happen when events are dispatched.

+ +

Because the Environment implements PSR-14’s ListenerProviderInterface you’ll also need to pass the configured Environment object to your event dispatcher so that it becomes aware of those available events.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event): void
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data->append('attributes/class', 'external-link');
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfiguration()->get('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = new Environment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convertToHtml($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/extensions/index.html b/2.1/customization/extensions/index.html new file mode 100644 index 0000000000..c757afaf66 --- /dev/null +++ b/2.1/customization/extensions/index.html @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a EnvironmentBuilderInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new EmojiExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/inline-parsing/index.html b/2.1/customization/inline-parsing/index.html new file mode 100644 index 0000000000..a5742757f3 --- /dev/null +++ b/2.1/customization/inline-parsing/index.html @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getMatchDefinition()

+ +

This method should return an instance of InlineParserMatch which defines the text the parser is looking for. Examples of this might be something like:

+ +
use League\CommonMark\Parser\Inline\InlineParserMatch;
+
+InlineParserMatch::string('@');                  // Match any '@' characters found in the text
+InlineParserMatch::string('foo');                // Match the text 'foo' (case insensitive)
+
+InlineParserMatch::oneOf('@', '!');              // Match either character
+InlineParserMatch::oneOf('http://', 'https://'); // Match either string
+
+InlineParserMatch::regex('\d+');                 // Match the regular expression (omit the regex delimiters and any flags)
+
+ +

Once a match is found, the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has found at a matching string in the current line; and,
  2. +
  3. No other inline parsers with a higher priority have successfully parsed the text at this point in the line
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser - see more information below.
  • +
+ +
InlineParserContext
+ +

This class has several useful methods:

+ +
    +
  • getContainer() - Returns the current container block the inline text was found in. You’ll almost always call $inlineContext->getContainer()->appendChild(...) to add the parsed inline text inside that block.
  • +
  • getReferenceMap() - Returns the document’s reference map
  • +
  • getCursor() - Returns the current Cursor used to parse the current line. (Note that the cursor will be positioned before the matched text, so you must advance it yourself if you determine it’s a valid match)
  • +
  • getDelimiterStack() - Returns the current delimiter stack. Only used in advanced use cases.
  • +
  • getFullMatch() - Returns the full string that matched you InlineParserMatch definition
  • +
  • getFullMatchLength() - Returns the length of the full match - useful for advancing the cursor
  • +
  • getSubMatches() - If your InlineParserMatch used a regular expression with capture groups, this will return the text matches by those groups.
  • +
  • getMatches() - Returns an array where index 0 is the “full match”, plus any sub-matches. It basically simulates preg_match()’s behavior.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the text at the current position for any reason. Other parsers will then have a chance to try parsing that text. If all registered parsers return false, the text will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed/matched text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::regex('@([A-Za-z0-9_]{1,15}(?!\w))');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+
+        // This seems to be a valid match
+        // Advance the cursor to the end of the match
+        $cursor->advanceBy($inlineContext->getFullMatchLength());
+
+        // Grab the Twitter handle
+        [$handle] = $inlineContext->getSubMatches();
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+// And here's how to hook it up:
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::oneOf(':)', ':(');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($inlineContext->getFullMatch() === ':)') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($inlineContext->getFullMatch() === ':(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance: +
      +
    • Avoid using overly-complex regular expressions in getMatchDefinition() - use the simplest regex you can and have parse() do the heavier validation
    • +
    • Have your parse() method return false as soon as possible.
    • +
    +
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/overview/index.html b/2.1/customization/overview/index.html new file mode 100644 index 0000000000..cc2fb239da --- /dev/null +++ b/2.1/customization/overview/index.html @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders/configuration you need
  2. +
  3. Instantiate a MarkdownParser and HtmlRenderer using that Environment
  4. +
  5. Use the MarkdownParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  6. +
  7. Use the HtmlRenderer to convert the AST Document into HTML
  8. +
+ +

The MarkdownConverter class handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Renderer\HtmlRenderer;
+
+$environment = new Environment([
+    'html_input' => 'strip',
+]);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderDocument($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renderers convert the parsed blocks/inlines from the AST representation into HTML. When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/rendering/index.html b/2.1/customization/rendering/index.html new file mode 100644 index 0000000000..8357c7652a --- /dev/null +++ b/2.1/customization/rendering/index.html @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + + Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Custom Rendering

+ +

Renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement NodeRendererInterface and its render() method. Note that in v2.0, both +block renderers and inline renderers share the same interface and method:

+ +

render()

+ +
public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The HtmlRenderer will call this method during the rendering process whenever a supported element is encountered.

+ +

If your renderer can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • Node $node - The encountered block or inline element that needs to be rendered
  • +
  • ChildNodeRendererInterface $childRenderer - If the given $node has children, use this to render those child elements
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the node and its contents, including any children. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\Util\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Renderers

+ +

When registering your renderer, you must tell the Environment which node element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// First param - the node class type that should use our renderer
+// Second param - instance of the renderer
+$environment->addRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple types:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
+use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements NodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(ThematicBreak::class, new TextDividerRenderer());
+
+ +

Note that thematic breaks should not contain children, which is why the $childRenderer is unused in this example. Otherwise we’d have to call code like this and return the result as part of the rendered HTML we’re generating here: $innerHtml = $childRenderer->renderNodes($node->children());

+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any child elements that your node might contain!
  • +
+ +

XML Rendering

+ +

The XML renderer will automatically attempt to convert any AST nodes to XML by inspecting the name of the block/inline node and its attributes. You can instead control the XML element name and attributes by making your renderer implement XmlNodeRendererInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+use League\CommonMark\Xml\XmlNodeRendererInterface;
+
+class TextDividerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+
+    public function getXmlTagName(Node $node): string
+    {
+        return 'text_divider';
+    }
+
+    public function getXmlAttributes(Node $node): array
+    {
+        return ['character' => '='];
+    }
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/customization/slug-normalizer/index.html b/2.1/customization/slug-normalizer/index.html new file mode 100644 index 0000000000..9e309377e2 --- /dev/null +++ b/2.1/customization/slug-normalizer/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + Slug Normalizer - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Slug Normalizer

+ +

“Slugs” are strings used within href, name, and id HTML attributes to identify particular elements within a document.

+ +

Some extensions (like the HeadingPermalinkExtension) need the ability to convert user-provided text into these URL-safe slugs while also ensuring that these are unique throughout the generated HTML. The Environment provides a pre-built normalizer you can use for this purpose.

+ +

Usage

+ +

You can obtain a reference to the built-in slug normalizer by calling $environment->getSlugNormalizer();

+ +

To use this within your extension, have your parser/renderer/whatever implement EnvironmentAwareInterface and then implement the corresponding setEnvironment method like this:

+ +

+use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Environment\EnvironmentAwareInterface;
+
+class MyCustomParserOrRenderer implements EnvironmentAwareInterface
+{
+    private $slugNormalizer;
+
+    public function setEnvironment(EnvironmentInterface $environment): void
+    {
+        $this->slugNormalizer = $environment->getSlugNormalizer();
+    }
+}
+
+ +

You can then call $this->slugNormalizer->normalize($text) as needed.

+ +

Configuration

+ +

The slug_normalizer configuration section allows you to adjust the following options:

+ +

instance

+ +

You can change the string that is used as the “slug” by setting the instance option to any class that implements TextNormalizerInterface. +We provide a simple SlugNormalizer by default, but you may want to plug in a different library or create your own normalizer instead.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

max_length

+ +

This can be configured to limit the length of that slug to prevent overly-long values. By default, that limit is 255 characters. You may set this to any positive integer, or 0 for no limit.

+ +

(Note that generated slugs might be slightly longer than this “limit” if the unique option is enabled and the slug generator detects a duplicate slug and needs to add a suffix to make it unique.)

+ +

unique

+ +

This options controls whether slugs should be unique. Possible values include:

+ +
    +
  • 'document' (string; default) - Ensures slugs are unique within a single document
  • +
  • 'environment' (string) - Ensures slugs are unique across multiple documents - see below
  • +
  • false (boolean) - Disables unique slug generation
  • +
+ +

You might have a use case where you’re converting several different Markdown documents on the same page and so you’d like to ensure that none of those documents use conflicting slugs. In that case, you should set the scope option to 'environment' to ensure that a single instance of a MarkdownConverter (which uses a single Environment) will never produce the same slug twice during its lifetime (which usually lasts the entire duration of a single HTTP request).

+ +

If you need complete control over how unique slugs are generated, make your 'instance' implement UniqueSlugNormalizerInterface; otherwise, we’ll simply append incremental numbers to slugs to ensure they are unique.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/attributes/index.html b/2.1/extensions/attributes/index.html new file mode 100644 index 0000000000..1b048dbe75 --- /dev/null +++ b/2.1/extensions/attributes/index.html @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+ +

This results in the following output:

+ +
<blockquote title="Blockquote title">
+<p>A nice blockquote</p>
+</blockquote>
+
+ +

CSS-selector-style declarations can be used to set the id and class attributes:

+ +
{#id .class}
+## Header
+
+ +

Output:

+ +
<h2 class="class" id="id">Header</h2>
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Output:

+ +
<p>This is <em style="color: red">red</em>.</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/autolinks/index.html b/2.1/extensions/autolinks/index.html new file mode 100644 index 0000000000..af37555bd5 --- /dev/null +++ b/2.1/extensions/autolinks/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a Mention Parser outside of this extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/commonmark/index.html b/2.1/extensions/commonmark/index.html new file mode 100644 index 0000000000..92397e4d92 --- /dev/null +++ b/2.1/extensions/commonmark/index.html @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically installed for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new Environment with the core extension
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/default-attributes/index.html b/2.1/extensions/default-attributes/index.html new file mode 100644 index 0000000000..63d22e6be9 --- /dev/null +++ b/2.1/extensions/default-attributes/index.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + Default Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Default Attributes

+ +

The DefaultAttributesExtension allows you to apply default HTML classes and other attributes using configuration options.

+ +

It works by applying the attributes to the nodes during the DocumentParsedEvent event - right after the nodes are parsed but before they are rendered. +(As a result, it’s possible that renderers may add other attributes - the goal of this extension is only to provide custom defaults.)

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DefaultAttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Node\Block\Paragraph;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'default_attributes' => [
+        Heading::class => [
+            'class' => static function (Heading $node) {
+                if ($node->getLevel() === 1) {
+                    return 'title-main';
+                } else {
+                    return null;
+                }
+            },
+        ],
+        Table::class => [
+            'class' => 'table',
+        ],
+        Paragraph::class => [
+            'class' => ['text-center', 'font-comic-sans'],
+        ],
+        Link::class => [
+            'class' => 'btn btn-link',
+            'target' => '_blank',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new DefaultAttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a default_attributes array. Each key in the array should be a FQCN for the node class you wish to apply the default attribute to, and the values should be a map of attribute names to attribute values.

+ +

Attribute values may be any of the following types:

+ +
    +
  • string
  • +
  • string[]
  • +
  • bool
  • +
  • callable (parameter is the Node, return value may be string|string[]|bool)
  • +
+ +

Examples

+ +

Here’s an example that will apply Bootstrap 4 classes and attributes:

+ +
$config = [
+    'default_attributes' => [
+        Table::class => [
+            'class' => ['table', 'table-responsive'],
+        ],
+        BlockQuote::class => [
+            'class' => 'blockquote',
+        ],
+    ],
+];
+
+ +

Here’s a more complex example that uses a callable to add a class only if the paragraph immediately follows an <h1> heading:

+ +
$config = [
+    'default_attributes' => [
+        Paragraph::class => [
+            'class' => static function (Paragraph $paragraph) {
+                if ($paragraph->previous() instanceof Heading && $paragraph->previous()->getLevel() === 1) {
+                    return 'lead';
+                }
+
+                return null;
+            },
+        ],
+    ],
+];
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/description-lists/index.html b/2.1/extensions/description-lists/index.html new file mode 100644 index 0000000000..7b37d33516 --- /dev/null +++ b/2.1/extensions/description-lists/index.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + Description List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Description List Extension

+ +

The DescriptionListExtension adds Markdown Extra-style description lists to facilitate the creation of <dl>, <dt>, and <dd> HTML using Markdown.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DescriptionListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\DescriptionList\DescriptionListExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DescriptionListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Some markdown goes here');
+
+ +

Syntax

+ +

The syntax is based directly on the rules and logic implemented by the Markdown Extra library. Here are some examples of sample Markdown input and HTML output demonstrating the syntax:

+ +
Apple
+:   Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.
+:   An American computer company.
+
+Orange
+:   The fruit of an evergreen tree of the genus Citrus.
+
+ +
<dl>
+    <dt>Apple</dt>
+    <dd>Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.</dd>
+    <dd>An American computer company.</dd>
+
+    <dt>Orange</dt>
+    <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
+</dl>
+
+ +

See the Markdown Extra documentation or our own spec for additional examples.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/disallowed-raw-html/index.html b/2.1/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..c2f227f50c --- /dev/null +++ b/2.1/extensions/disallowed-raw-html/index.html @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically escapes certain HTML tags when rendering raw HTML, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Customize the extension's configuration if needed
+// Default values are shown below - you can omit this configuration if you're happy with those defaults
+// and don't want to customize them
+$config = [
+    'disallowed_raw_html' => [
+        'disallowed_tags' => ['title', 'textarea', 'style', 'xmp', 'iframe', 'noembed', 'noframes', 'script', 'plaintext'],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I cannot change the page <title>anymore</title>');
+
+ +

Configuration

+ +

This extension can be configured by providing a disallowed_raw_html array with the following nested configuration options. The defaults are shown in the code example above.

+ +

disallowed_tags

+ +

An array containing a list of tags that should be escaped.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/external-links/index.html b/2.1/extensions/external-links/index.html new file mode 100644 index 0000000000..dcd8129248 --- /dev/null +++ b/2.1/extensions/external-links/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class MyCustomLinkRenderer implements NodeRendererInterface
+{
+    /**
+     * @param Node                       $node
+     * @param ChildNodeRendererInterface $childRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        if (!($node instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible node type: ' . \get_class($node));
+        }
+
+        if ($node->data->get('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to automagically add an external link icon by targeting the html_class given in the configuration:

+ +
// Font Awesome example:
+a[target="_blank"]::after,
+a.external::after {
+   content: "\f08e";
+   font: normal normal normal 14px/1 FontAwesome;
+}
+
+// Glyphicon example:
+a[target="_blank"]::after,
+a.external::after {
+  @extend .glyphicon;
+  content: "\e164";
+  margin-left: .5em;
+  margin-right: .25em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/footnotes/index.html b/2.1/extensions/footnotes/index.html new file mode 100644 index 0000000000..d539782c9c --- /dev/null +++ b/2.1/extensions/footnotes/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1"></a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'backref_symbol'     => '↩',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

backref_symbol

+ +

This string option sets the symbol used as the contents of the footnote backreference link. It defaults to \League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer::DEFAULT_SYMBOL = '↩'.

+ +

If you want to use a custom icon, set this to an empty string '' and take a look at the Adding Icons section below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ +

Adding Icons

+ +

You can use CSS to add a custom icon instead of providing a backref_symbol:

+ +
$config = [
+    'footnote' => [
+        'backref_class' => 'footnote-backref',
+        'backref_symbol' => '',
+    ],
+];
+
+ +

Then target the backref_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.footnote-backref::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/front-matter/index.html b/2.1/extensions/front-matter/index.html new file mode 100644 index 0000000000..64185f5b6c --- /dev/null +++ b/2.1/extensions/front-matter/index.html @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + Front Matter Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Front Matter Extension

+ +

The FrontMatterExtension adds the ability to parse YAML front matter from the Markdown document and include that in the return result.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

You will also need to install symfony/yaml or the YAML extension for PHP to use this extension. For symfony/yaml:

+ +
composer require symfony/yaml
+
+ +

(You can use any version of symfony/yaml 2.3 or higher, though we recommend using 4.0 or higher.)

+ +

Front Matter Syntax

+ +

This extension follows the Jekyll Front Matter syntax. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Here is a basic example:

+ +
---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+
+ +

This will produce a front matter array similar to this:

+ +
$parsedFrontMatter = [
+    'layout' => 'post',
+    'title' => 'I Love Markdown',
+    'tags' => [
+        'test',
+        'example',
+    ],
+];
+
+ +

And the HTML output will only contain the one heading:

+ +
<h1>Hello World!</h1>
+
+ +

Usage

+ +

Configure your Environment as usual and add the FrontMatterExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FrontMatterExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+// A sample Markdown file with some front matter:
+$markdown = <<<MD
+---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+MD;
+
+$result = $converter->convertToHtml($markdown);
+
+// Grab the front matter:
+if ($result instanceof RenderedContentWithFrontMatter) {
+    $frontMatter = $result->getFrontMatter();
+}
+
+// Output the HTML using any of these:
+echo $result;               // implicit string cast
+// or:
+echo (string) $result;      // explicit string cast
+// or:
+echo $result->getContent();
+
+ +

Parsing Front Matter Only

+ +

You don’t have to parse the entire file (including all the Markdown) if you only want the front matter. You can either instantiate the front matter parser yourself and call it directly, like this:

+ +
use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+// For `symfony/yaml`
+$frontMatterParser = new FrontMatterParser(new SymfonyYamlFrontMatterParser());
+// For YAML extension
+$frontMatterParser = new FrontMatterParser(new LibYamlFrontMatterParser());
+$result = $frontMatterParser->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

Or you can use the getFrontMatterParser() method from the extension:

+ +
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+$frontMatterExtension = new FrontMatterExtension();
+$result = $frontMatterExtension->getFrontMatterParser()->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

This latter approach may be more convenient if you have already instantiated a FrontMatterExtension object you’re adding to the Environment somewhere and just want to call that.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/github-flavored-markdown/index.html b/2.1/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..0912578d0d --- /dev/null +++ b/2.1/extensions/github-flavored-markdown/index.html @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark and GFM parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/heading-permalinks/index.html b/2.1/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..8f957c7851 --- /dev/null +++ b/2.1/extensions/heading-permalinks/index.html @@ -0,0 +1,615 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\MarkdownConverter;
+
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'content',
+        'fragment_prefix' => 'content',
+        'insert' => 'before',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+        'aria_hidden' => true,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

fragment_prefix

+ +

This should be a string you want prepended to the URL fragment in the link’s href attribute. This should typically be set to the same value as id_prefix for links to work properly. However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to “rewrite” them.

+ +

For example, to emulate how GitHub heading permalinks work, set id_prefix to 'user-content', set fragment_prefix to '', and insert some JavaScript into the page like this:

+ +
var scrollToPermalink = function() {
+    var link = document.getElementById('user-content-' + window.location.hash);
+    if (link) {
+        link.scrollIntoView({behavior: 'smooth'});
+    }
+};
+
+window.addEventListener('hashchange', scrollToPermalink);
+if (window.location.hash) {
+    scrollToPermalink();
+}
+
+ +

insert

+ +

This controls whether the anchor is added to the beginning of the <h1>, <h2> etc. tag or to the end. Can be set to either 'before' or 'after'.

+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should have permalinks added. By default, all 6 levels (1, 2, 3, 4, 5, and 6) will have them. You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

aria_hidden

+ +

This option sets the aria-hidden attribute on the <a> tag. This defaults to aria-hidden="true".

+ +

Setting this option to false would render the <a> tag excluding the aria-hidden entirely.

+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/inlines-only/index.html b/2.1/extensions/inlines-only/index.html new file mode 100644 index 0000000000..b51e241971 --- /dev/null +++ b/2.1/extensions/inlines-only/index.html @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new, empty environment
+$environment = new Environment($config);
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/mentions/index.html b/2.1/extensions/mentions/index.html new file mode 100644 index 0000000000..096b691ea0 --- /dev/null +++ b/2.1/extensions/mentions/index.html @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which prefix you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting prefix, a regular expression to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same prefix, the first mention parser to
+        // successfully match and return a properly constructed Mention object (where the URL has been set) will be the
+        // the mention parser that is used. In this example, the GitHub handle would actually match first because
+        // there isn't any real validation to check whether https://www.github.com/colinodell exists. However, in
+        // CMS applications, you could check whether its a local user first, then check Twitter and then GitHub, etc.
+        'twitter_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[A-Za-z0-9_]{1,15}(?!\w)',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Follow me on GitHub: @colinodell');
+// Output:
+// <p>Follow me on GitHub: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Prefix └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the prefix (in this case, @). You can use any prefix, regular expression pattern (without opening/closing delimiter or modifiers), or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Node\Inline\AbstractInline;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Inline\Element\AbstractInline;
+
+class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'user_url_generator' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z0-9]+',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/overview/index.html b/2.1/extensions/overview/index.html new file mode 100644 index 0000000000..4412f8ab55 --- /dev/null +++ b/2.1/extensions/overview/index.html @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Default AttributesEasily apply default HTML classes using configuration options to match your site’s styles2.0.0 
Description ListsCreate <dl> description lists using Markdown Extra’s syntax2.0.0 
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
Front MatterParses YAML front matter from your Markdown input2.0.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the extensions you need
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the other extensions you need
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/smart-punctuation/index.html b/2.1/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..f6aeb194d5 --- /dev/null +++ b/2.1/extensions/smart-punctuation/index.html @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/strikethrough/index.html b/2.1/extensions/strikethrough/index.html new file mode 100644 index 0000000000..a00ade166a --- /dev/null +++ b/2.1/extensions/strikethrough/index.html @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/table-of-contents/index.html b/2.1/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..f3a34191ce --- /dev/null +++ b/2.1/extensions/table-of-contents/index.html @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/tables/index.html b/2.1/extensions/tables/index.html new file mode 100644 index 0000000000..c7d34e3b68 --- /dev/null +++ b/2.1/extensions/tables/index.html @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convertToHtml('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/extensions/task-lists/index.html b/2.1/extensions/task-lists/index.html new file mode 100644 index 0000000000..9c9a1f6673 --- /dev/null +++ b/2.1/extensions/task-lists/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convertToHtml($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/index.html b/2.1/index.html new file mode 100644 index 0000000000..2fa029fe3c --- /dev/null +++ b/2.1/index.html @@ -0,0 +1,443 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello, World!')->getContent();
+
+// <h1>Hello, World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/installation/index.html b/2.1/installation/index.html new file mode 100644 index 0000000000..b2a36db4f4 --- /dev/null +++ b/2.1/installation/index.html @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +
composer require "league/commonmark:^2.1"
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^2.1. This is equivalent to >=2.1 <3.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/security/index.html b/2.1/security/index.html new file mode 100644 index 0000000000..43daf6d90d --- /dev/null +++ b/2.1/security/index.html @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convertToHtml('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convertToHtml($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/support/index.html b/2.1/support/index.html new file mode 100644 index 0000000000..457c661fb6 --- /dev/null +++ b/2.1/support/index.html @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + Support - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Support

+ +

Here are some useful resources to help you use this project:

+ + + +

Supported Versions

+ +

See our security policy for information about the support cycle for bug fixes and security updates.

+ +

Reporting a Vulnerability

+ +

If you discover a security vulnerability within this package, please use the Tidelift security contact form or email Colin O’Dell at colinodell@gmail.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/upgrading/index.html b/2.1/upgrading/index.html new file mode 100644 index 0000000000..443c098d5c --- /dev/null +++ b/2.1/upgrading/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + Upgrading from 2.0 to 2.1 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 2.0 to 2.1

+ +

No changes or deprecations were made that require changes to upgrade to the new version.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.1/xml/index.html b/2.1/xml/index.html new file mode 100644 index 0000000000..86fe2696dd --- /dev/null +++ b/2.1/xml/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + XML Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.1. Please consider upgrading your code to the latest stable version

+ + +

XML Rendering

+ +

Version 2.0 introduced the ability to render Markdown Document objects in XML. This is particularly useful for debugging custom extensions.

+ +

To convert Markdown to XML, you would instantiate an Environment, parse the Markdown into an AST, and render it using the new XmlRenderer:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Xml\XmlRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$renderer = new XmlRenderer($environment);
+
+$document = $parser->parse('# **Hello** World!');
+
+echo $renderer->renderDocument($document);
+
+ +

This will display XML output like this:

+ +
<?xml version="1.0" encoding="UTF-8"?>
+<document xmlns="http://commonmark.org/xml/1.0">
+    <heading level="1">
+        <strong>
+            <text>Hello</text>
+        </strong>
+        <text> World!</text>
+    </heading>
+</document>
+
+ +

Return Value

+ +

Like with CommonMarkConverter::convertToHtml(), the renderDocument() actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final XML output.

+ +

Customizing the XML Output

+ +

See the rendering documentation for information on customizing the XML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/basic-usage/index.html b/2.2/basic-usage/index.html new file mode 100644 index 0000000000..087cf16f56 --- /dev/null +++ b/2.2/basic-usage/index.html @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Using Extensions

+ +

The CommonMarkConverter and GithubFlavoredMarkdownConverter shown above automatically configure the environment for you, but if you want to use additional extensions you’ll need to avoid those classes and use the generic MarkdownConverter class instead to customize the environment with whatever extensions you wish to use:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+$environment->addExtension(new InlinesOnlyExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+// <p><strong>Hello World!</strong></p>
+
+ +

Configuration

+ +

If you’re using the CommonMarkConverter or GithubFlavoredMarkdownConverter class you can pass configuration options directly into their constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

See the configuration section for more information on the available configuration options.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ +

Return Value

+ +

The convert() method actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final HTML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/changelog/index.html b/2.2/changelog/index.html new file mode 100644 index 0000000000..be7687b826 --- /dev/null +++ b/2.2/changelog/index.html @@ -0,0 +1,788 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 2.x releases are shown below. See the full list of releases for the complete changelog.

+ +

2.5.3 - 2024-08-16

+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.1, including: +
      +
    • Remove source, add search to list of recognized block tags
    • +
    +
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.2…2.5.3

+ +

2.5.2 - 2024-08-14

+ +

Changed

+ +
    +
  • Boolean attributes now require an explicit true value (#1040)
  • +
+ +

Fixed

+ +
    +
  • Fixed regression where text could be misinterpreted as an attribute (#1040)
  • +
+ +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.1…2.5.2

+ +

2.5.1 - 2024-07-24

+ +

Fixed

+ +
    +
  • Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
  • +
  • Fixed incorrect Table start line numbers (#1037)
  • +
+ +

New Contributors

+
    +
  • @jasonvarga made their first contribution in https://github.com/thephpleague/commonmark/pull/1035
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.0…2.5.1

+ +

2.5.0 - 2024-07-22

+ +

Added

+ +
    +
  • The AttributesExtension now supports attributes without values (#985, #986)
  • +
  • The AutolinkExtension exposes two new configuration options to override the default behavior (#969, #987): +
      +
    • autolink/allowed_protocols - an array of protocols to allow autolinking for
    • +
    • autolink/default_protocol - the default protocol to use when none is specified
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.0, including: +
      +
    • Allow closing fence to be followed by tabs
    • +
    • Remove restrictive limitation on inline comments
    • +
    • Unicode symbols now treated like punctuation (for purposes of flankingness)
    • +
    • Trailing tabs on the last line of indented code blocks will be excluded
    • +
    • Improved HTML comment matching
    • +
    +
  • +
  • Paragraphs only containing link reference definitions will be kept in the AST until the Document is finalized +
      +
    • (These were previously removed immediately after parsing the Paragraph)
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed list tightness not being determined properly in some edge cases
  • +
  • Fixed incorrect ending line numbers for several block types in various scenarios
  • +
  • Fixed lowercase inline HTML declarations not being accepted
  • +
+ +

New Contributors

+
    +
  • @svenluijten made their first contribution in https://github.com/thephpleague/commonmark/pull/986
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.4…2.5.0

+ +

2.4.4 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.3…2.4.4

+ +

2.4.3 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
  • +
  • Fixed UrlAutolinkParser incorrectly parsing text containing www anywhere before an autolink (#1025)
  • +
+ +

New Contributors

+
    +
  • @nfreader made their first contribution in https://github.com/thephpleague/commonmark/pull/1016
  • +
  • @macbookandrew made their first contribution in https://github.com/thephpleague/commonmark/pull/1025
  • +
  • @xavierlacot made their first contribution in https://github.com/thephpleague/commonmark/pull/1013
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.2…2.4.3

+ +

2.4.2 - 2024-02-02

+ +

Fixed

+ +
    +
  • Fixed declaration parser being too strict
  • +
  • FencedCodeRenderer: don’t add language- to class if already prefixed
  • +
+ +

New Contributors

+
    +
  • @sergiy-petrov made their first contribution in https://github.com/thephpleague/commonmark/pull/997
  • +
  • @clustermin made their first contribution in https://github.com/thephpleague/commonmark/pull/988
  • +
  • @peter279k made their first contribution in https://github.com/thephpleague/commonmark/pull/996
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.1…2.4.2

+ +

2.4.1 - 2023-08-30

+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not fully disabling the rel attribute when configured to do so (#992)
  • +
+ +

2.4.0 - 2023-03-24

+ +

See the upgrading guide for more information about the exception-related changes

+ +

Added

+ +
    +
  • Added generic CommonMarkException marker interface for all exceptions thrown by the library
  • +
  • Added several new specific exception types implementing that marker interface: +
      +
    • AlreadyInitializedException
    • +
    • InvalidArgumentException
    • +
    • IOException
    • +
    • LogicException
    • +
    • MissingDependencyException
    • +
    • NoMatchingRendererException
    • +
    • ParserLogicException
    • +
    +
  • +
  • Added more configuration options to the Heading Permalinks extension (#939): +
      +
    • heading_permalink/apply_id_to_heading - When true, the id attribute will be applied to the heading element itself instead of the <a> tag
    • +
    • heading_permalink/heading_class - class to apply to the heading element
    • +
    • heading_permalink/insert - now accepts none to prevent the creation of the <a> link
    • +
    +
  • +
  • Added new table/alignment_attributes configuration option to control how table cell alignment is rendered (#959)
  • +
+ +

Changed

+ +
    +
  • Change several thrown exceptions from RuntimeException to LogicException (or something extending it), including: +
      +
    • CallbackGenerators that fail to set a URL or return an expected value
    • +
    • MarkdownParser when deactivating the last block parser or attempting to get an active block parser when they’ve all been closed
    • +
    • Adding items to an already-initialized Environment
    • +
    • Rendering a Node when no renderer has been registered for it
    • +
    +
  • +
  • HeadingPermalinkProcessor now throws InvalidConfigurationException instead of RuntimeException when invalid config values are given.
  • +
  • HtmlElement::setAttribute() no longer requires the second parameter for boolean attributes
  • +
  • Several small micro-optimizations
  • +
  • Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
  • +
+ +

Fixed

+ +
    +
  • Fixed inaccurate @throws docblocks throughout the codebase, including ConverterInterface, MarkdownConverter, and MarkdownConverterInterface. +
      +
    • These previously suggested that only \RuntimeExceptions were thrown, which was inaccurate as \LogicExceptions were also possible.
    • +
    +
  • +
+ +

2.3.9 - 2023-02-15

+ +

Fixed

+ +
    +
  • Fixed autolink extension not detecting some URIs with underscores (#956)
  • +
+ +

2.3.8 - 2022-12-10

+ +

Fixed

+ +
    +
  • Fixed parsing issues when mb_internal_encoding() is set to something other than UTF-8 (#951)
  • +
+ +

2.3.7 - 2022-11-17

+ +

Fixed

+ +
    +
  • Fixed TaskListItemMarkerRenderer not including HTML attributes set on the node by other extensions (#947)
  • +
+ +

2.3.6 - 2022-10-30

+ +

Fixed

+ +
    +
  • Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a .) (#943)
  • +
+ +

2.3.5 - 2022-07-29

+ +

Fixed

+ +
    +
  • Fixed error using InlineParserEngine when no inline parsers are registered in the Environment (#908)
  • +
+ +

2.3.4 - 2022-07-17

+ +

Changed

+ +
    +
  • Made a number of small tweaks to the embed extension’s parsing behavior to fix #898: +
      +
    • Changed EmbedStartParser to always capture embed-like lines in container blocks, regardless of parent block type
    • +
    • Changed EmbedProcessor to also remove Embed blocks that aren’t direct children of the Document
    • +
    • Increased the priority of EmbedProcessor to 1010
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed EmbedExtension not parsing embeds following a list block (#898)
  • +
+ +

2.3.3 - 2022-06-07

+ +

Fixed

+ +
    +
  • Fixed DomainFilteringAdapter not reindexing the embed list (#884, #885)
  • +
+ +

2.3.2 - 2022-06-03

+ +

Fixed

+ +
    +
  • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
  • +
+ +

2.2.5 - 2022-06-03

+ +

Fixed

+ +
    +
  • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
  • +
+ +

2.3.1 - 2022-05-14

+ +

Fixed

+ +
    +
  • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
  • +
+ +

2.2.4 - 2022-05-14

+ +

Fixed

+ +
    +
  • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
  • +
+ +

2.3.0 - 2022-04-07

+ +

Added

+ +
    +
  • Added new EmbedExtension (#805)
  • +
  • Added DocumentRendererInterface as a replacement for the now-deprecated MarkdownRendererInterface
  • +
+ +

Deprecated

+ +
    +
  • Deprecated MarkdownRendererInterface; use DocumentRendererInterface instead
  • +
+ +

2.2.3 - 2022-02-26

+ +

Fixed

+ +
    +
  • Fixed front matter parsing with Windows line endings (#821)
  • +
+ +

2.2.2 - 2022-02-13

+ +

Fixed

+ +
    +
  • Fixed double-escaping of image alt text (#806, #810)
  • +
  • Fixed Psalm typehints for event class names
  • +
+ +

2.2.1 - 2022-01-25

+ +

Fixed

+ +
    +
  • Fixed symfony/deprecation-contracts constraint
  • +
+ +

Removed

+ +
    +
  • Removed deprecation trigger from MarkdownConverterInterface to reduce noise
  • +
+ +

2.2.0 - 2022-01-22

+ +

Added

+ +
    +
  • Added new ConverterInterface
  • +
  • Added new MarkdownToXmlConverter class
  • +
  • Added new HtmlDecorator class which can wrap existing renderers with additional HTML tags
  • +
  • Added new table/wrap config to apply an optional wrapping/container element around a table (#780)
  • +
+ +

Changed

+ +
    +
  • HtmlElement contents can now consist of any Stringable, not just HtmlElement and string
  • +
+ +

Deprecated

+ +
    +
  • Deprecated MarkdownConverterInterface and its convertToHtml() method; use ConverterInterface and convert() instead
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/configuration/index.html b/2.2/configuration/index.html new file mode 100644 index 0000000000..81327a6a45 --- /dev/null +++ b/2.2/configuration/index.html @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

Many aspects of this library’s behavior can be tweaked using configuration options.

+ +

You can provide an array of configuration options to the Environment or converter classes when creating them:

+ +
$config = [
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'commonmark' => [
+        'enable_em' => true,
+        'enable_strong' => true,
+        'use_asterisk' => true,
+        'use_underscore' => true,
+        'unordered_list_markers' => ['-', '*', '+'],
+    ],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => PHP_INT_MAX,
+    'slug_normalizer' => [
+        'max_length' => 255,
+    ],
+];
+
+ +

If you’re using the basic CommonMarkConverter or GithubFlavoredMarkdown classes, simply pass the configuration array into the constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

Here’s a list of the core configuration options available:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: PHP_INT_MAX). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested.
  • +
  • slug_normalizer - Array of options for configuring how URL-safe slugs are created; see the slug normalizer docs for more details +
      +
    • instance - An alternative normalizer to use (defaults to the included SlugNormalizer)
    • +
    • max_length - Limits the size of generated slugs (defaults to 255 characters)
    • +
    • unique - Controls whether slugs should be unique per 'document' (default) or per 'environment'; can be disabled with false
    • +
    +
  • +
+ +

Additional configuration options are available for most of the available extensions - refer to their individual documentation for more details. For example, the CommonMark core extension offers these additional options:

+ +
    +
  • commonmark - Array of options for configuring the CommonMark core extension: +
      +
    • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
    • +
    • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
    • +
    • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
    • +
    • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
    • +
    • unordered_list_markers - Array of characters that can be used to indicate a bulleted list (default: ["-", "*", "+"])
    • +
    +
  • +
+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/abstract-syntax-tree/index.html b/2.2/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..bd3eeaa9ed --- /dev/null +++ b/2.2/customization/abstract-syntax-tree/index.html @@ -0,0 +1,702 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the MarkdownParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent see the (Event Dispatcher documentation)
  • +
+ +

Visualization

+ +

Even with an interactive debugger it can be tricky to view an entire tree at once. Consider using the XmlRenderer to provide a simple text-based representation of the AST for debugging purposes.

+ +

Node Traversal

+ +

There are four different ways to traverse/iterate the Nodes within the AST:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodProsCons
Manual TraversalBest way to access/check direct relatives of nodesNot useful for iteration
Iterating the TreeFast and efficientPossible unexpected behavior when adding/removing sibling nodes while iterating
Walking the TreeFull control over iterationUp to twice as slow as iteration; adding/removing nodes while iterating can lead to weird behaviors
Querying NodesEasier to write and understand; no weird behaviorsNot memory efficient
+ +

Each is described in more detail below

+ +

Manual Traversal

+ +

The following methods can be used to manually traverse from one Node to any of its direct relatives:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

This is best suited for situations when you need to know information about those relatives.

+ +

Iterating the Tree

+ +

If you’d like to iterate through all the nodes, use the iterator() method to obtain an iterator that will loop through each node in the tree (using pre-order traversal):

+ +
foreach ($document->iterator() as $node) {
+    echo 'Current node: ' . get_class($node) . "\n";
+}
+
+ +

Given an AST like this (XML representation):

+ +
<document>
+  <heading level="1">
+    <text>Hello World!</text>
+  </heading>
+  <paragraph>
+    <text>This is an example of </text>
+    <strong>
+      <text>CommonMark</text>
+    </strong>
+    <text>.</text>
+  </paragraph>
+</document>
+
+ +

The code above will output:

+ +
Current node: League\CommonMark\Node\Block\Document
+Current node: League\CommonMark\Extension\CommonMark\Node\Block\Heading
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Block\Paragraph
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Extension\CommonMark\Node\Inline\Strong
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Inline\Text
+
+ +

This iterator doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes. It’s also very CPU and memory-efficient.

+ +

Be careful when modifying nodes while iterating the tree as some of those changes may affect the current iteration process, especially for sibling nodes that come after the current one. For example, if you remove the current node’s next() sibling, the next loop of that iteration will still include the removed sibling even though it was successfully removed from the AST. Similarly, any new siblings that are added won’t be visited on the next loop.

+ +

Walking the Tree

+ +

If you’d like to walk through all the nodes, visiting each one as you enter and leave it, use the walker() method to obtain an instance of NodeWalker. This also uses pre-order traversal but emitting NodeWalkerEvents along the way:

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'Now ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

Using the same example AST in the previous section, this code will output:

+ +
Now entering a League\CommonMark\Node\Block\Document node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Block\Paragraph node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Node\Block\Paragraph node
+Now leaving a League\CommonMark\Node\Block\Document node
+
+ +

This approach offers many of the same benefits as the simple iteration shown in the previous section such as memory efficiency and no recursion. The key differences come from how you enter and leave nodes:

+ +
    +
  1. Iteration can potentially take twice as long - not ideal for performance
  2. +
  3. Provides you with more control over exactly when an action is taken on a node which is sometimes needed for certain AST manipulations
  4. +
  5. Also provides a resumeAt() method to override where it should iterate next
  6. +
+ +

But like with the iterator, be careful when adding/removing nodes while walking the tree, as there are even more subtle cases where the walker could even lose track of where it was, which may result in some nodes being visited multiple times or not at all.

+ +

Querying Nodes

+ +

If you’re trying to locate certain nodes to perform actions on them, querying the nodes from the AST might be easier to implement. This can be done with the Query class:

+ +
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Node\Block\Paragraph;
+use League\CommonMark\Node\Query;
+
+// Find all paragraphs and blockquotes that contain links
+$matchingNodes = (new Query())
+    ->where(Query::type(Paragraph::class))
+    ->orWhere(Query::type(BlockQuote::class))
+    ->andWhere(Query::hasChild(Query::type(Link::class)))
+    ->findAll($document);
+
+foreach ($matchingNodes as $node) {
+    // TODO: Do something with them
+}
+
+ +

Each condition passed into where(), orWhere(), or andWhere() must be a callable “filter” that accepts a Node and returns true or false. We provide several methods that can help create these filters for you:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Query::type(string $class)Creates a filter that matches nodes with the given class name
Query::hasChild()Creates a filter that matches nodes which contain at least one child
Query::hasChild(callable $condition)Creates a filter that matches nodes which contain at least one child that matches the inner $condition
Query::hasParent()Creates a filter that matches nodes which have a parent
Query::hasParent(callable $condition)Creates a filter that matches nodes which have a parent that matches the inner $condition
+ +

You can of course create your own custom filters/conditions using an anonymous function or by implementing ExpressionInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Node\Query;
+use League\CommonMark\Node\Query\ExpressionInterface;
+
+class ChildCountGreaterThan implements ExpressionInterface
+{
+    private $count;
+
+    public function __construct(int $count)
+    {
+        $this->count = $count;
+    }
+
+    public function __invoke(Node $node) : bool{
+        return count($node->children()) > $this->count;
+    }
+}
+
+$query = (new Query())
+    ->where(function (Node $node): bool { return $node->data->has('attributes/class'); })
+    ->andWhere(new ChildCountGreaterThan(3));
+
+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ +

Data Storage

+ +

Each Node has a property called data which is a Data (array-like) object. This can be used to store any arbitrary data you’d like on the node:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text1 = new Text('Hello, world!');
+$text1->data->set('language', 'English');
+$text1->data->set('is_good_translation', true);
+
+$text2 = new Text('Bonjour monde!');
+$text2->data->set('language', 'French');
+$text2->data->set('is_good_translation', false);
+
+foreach ([$text1, $text2] as $text) {
+    if ($text->data->get('is_good_translation')) {
+        sprintf('In %s we would say: "%s"', $text->data->get('language'), $text->getLiteral());
+    } else {
+        sprintf('I think they would say "%s" in %s, but I\'m not sure.', $text->getLiteral(), $text->data->get('language'));
+    }
+}
+
+ +

You can also access deeply-nested paths using / or . as delimiters:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text = new Text('Hello, world!');
+$text->data->set('info', ['language' => 'English', 'is_good_translation' => true]);
+
+var_dump($text->data->get('info/language'));
+var_dump($text->data->get('info.is_good_translation'));
+
+$text->data->set('info/is_example', true);
+
+ +

HTML Attributes

+ +

The data property comes pre-instantiated with a single data element called attributes which is used to store any HTML attributes that need to be rendered. For example:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+$link = new Link('https://twitter.com/colinodell', '@colinodell');
+$link->data->append('attributes/class', 'social-link');
+$link->data->append('attributes/class', 'twitter');
+$link->data->set('attributes/target', '_blank');
+$link->data->set('attributes/rel', 'noopener');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/block-parsing/index.html b/2.2/customization/block-parsing/index.html new file mode 100644 index 0000000000..d4a9ce3f6f --- /dev/null +++ b/2.2/customization/block-parsing/index.html @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

At a high level, block parsing is a two-step process:

+ +
    +
  1. Using a BlockStartParserInterface to identify if/where a block start exists on the given line
  2. +
  3. Using a BlockContinueParserInterface to perform additional processing of the identified block
  4. +
+ +

So to implement a custom block parser you will actually need to implement both of these classes.

+ +

BlockStartParserInterface

+ +

Instances of this interface have a single tryStart() method:

+ +
/**
+ * Check whether we should handle the block at the current position
+ *
+ * @param Cursor                       $cursor
+ * @param MarkdownParserStateInterface $parserState
+ *
+ * @return BlockStart|null
+ */
+public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart;
+
+ +

Given a Cursor at the current position, plus some extra information about the state of the parser, this method is responsible for determining whether a particular type of block seems to exist at the given position. You don’t actually parse the block here - that’s the job of a BlockContinueParserInterface. Your only job here is to return whether or not a particular type of block does exist here, and if so which block parser should parse it.

+ +

If you find that you cannot parse the given block, you should return BlockStart::none(); from this function.

+ +

However, if the Markdown at the current position does indeed seem to be the type of block you’re looking for, you should return a BlockStart instance using the following static constructor pattern:

+ +
use League\CommonMark\Parser\Block\BlockStart;
+
+return BlockStart::of(new MyCustomParser())->at($cursor);
+
+ +

Unlike in 1.x, the Cursor state is no longer shared between parsers. You must therefore explicitly provide the BlockStart object with a copy of your cursor at the correct, post-parsing position.

+ +

NOTE: If your custom block starts with a letter character you’ll need to add your parser to the environment with a priority of 250 or higher. This is due to a performance optimization where such lines are usually skipped.

+ +

BlockContinueParserInterface

+ +

The previous interface only helps the engine identify where a block starts. Additional information about the block, as well as the ability to parse additional lines of input, is all handled by the BlockContinueParserInterface.

+ +

This interface has several methods, so it’s usually easier to extend from AbstractBlockContinueParser instead, which sets most of the methods to use typical defaults you can override as needed.

+ +

getBlock()

+ +
public function getBlock(): AbstractBlock;
+
+ +

Each instance of a BlockContinueParserInterface is associated with a new block that is being parsed. This method here returns that block.

+ +

isContainer()

+ +
public function isContainer(): bool;
+
+ +

This method returns whether or not the block is a “container” capable of containing other blocks as children.

+ +

canContain()

+ +
public function canContain(AbstractBlock $childBlock): bool;
+
+ +

This method returns whether the current block being parsed can contain the given child block.

+ +

canHaveLazyContinuationLines()

+ +
public function canHaveLazyContinuationLines(): bool;
+
+ +

This method returns whether or not this parser should also receive subsequent lines of Markdown input. This is primarily used when a block can span multiple lines, like code blocks do.

+ +

addLine()

+ +
public function addLine(string $line): void;
+
+ +

If canHaveLazyContinuationLines() returned true, this method will be called with the additional lines of content.

+ +

tryContinue()

+ +
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue;
+
+ +

This method allows you to try and parse an additional line of Markdown.

+ +

closeBlock()

+ +
public function closeBlock(): void;
+
+ +

This method is called when the block is done being parsed. Any final adjustments to the block should be made at this time.

+ +

parseInlines()

+ +
public function parseInlines(InlineParserEngineInterface $inlineParser): void;
+
+ +

This method is called when the engine is ready to parse any inline child elements.

+ +

Note: For performance reasons, this method is not part of BlockContinueParserInterface. If your block may contain inlines, you should make sure that your “continue parser” also implements BlockContinueParserWithInlinesInterface.

+ +

Tips

+ +

Here are some additional tips to consider when writing your own custom parsers:

+ +

Combining both into one file

+ +

Although parsing requires two classes, you can use the anonymous class feature of PHP to combine both into a single file! Here’s an example:

+ +
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
+use League\CommonMark\Parser\Block\BlockStartParserInterface;
+
+final class MyCustomBlockParser extends AbstractBlockContinueParser
+{
+    // TODO: implement your continuation parsing methods here
+
+    public static function createBlockStartParser(): BlockStartParserInterface
+    {
+        return new class implements BlockStartParserInterface
+        {
+            // TODO: implement the tryStart() method here
+        };
+    }
+}
+
+
+ +

Performance

+ +

The BlockStartParserInterface::tryStart() and BlockContinueParserInterface::tryContinue() methods may be called hundreds or thousands of times during execution. For best performance, have your methods return as early as possible, and make sure your code is highly optimized.

+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

If your block contains literal strings/text within the block (and not as part of a child block), you should have your custom block type also implement StringContainerInterface.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/configuration/index.html b/2.2/customization/configuration/index.html new file mode 100644 index 0000000000..3cc650131c --- /dev/null +++ b/2.2/customization/configuration/index.html @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Configuration Schemas and Values

+ +

Version 2.0 introduced a new robust system for defining configuration schemas and accessing them within custom extensions.

+ +

Configuration Schemas

+ +

Unlike in 1.x, all configuration options must have a defined schema. This defines which options are available, what types of values they accept, whether any are required, and any default values you wish to define if the user doesn’t provide any.

+ +

These custom options can be defined from within your custom extension by implementing the ConfigurableExtensionInterface:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+use Nette\Schema\Expect;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        $builder->addSchema('my_extension', Expect::structure([
+            'enable_some_feature' => Expect::bool()->default(true),
+            'html_class' => Expect::string()->default('my-custom-extension'),
+            'align' => Expect::anyOf('left', 'center', 'right')->default('left'),
+            'favorite_number' => Expect::int()->min(1)->max(100)->default(42),
+        ]));
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        // TODO: Implement register() method
+    }
+}
+
+ +

See the league/config documentation for more examples of how to define custom configuration schemas.

+ +

Note that you only need to implement ConfigurableExtensionInterface if you plan to define new configuration options - you don’t need this if you’re only reading existing options.

+ +

Reading Configuration Values

+ +

Okay, so your extension has defined the different options that are available, but now you want to start using them within your custom extension. There are a few ways you can access the values:

+ +

During Extension Registration

+ +

Perhaps your extension needs to decide whether/how to register certain parsers/renderers/etc based on the user-provided configuration values - in that case, you can read the value from the $environment - for example:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        // (see code example above)
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        if ($environment->getConfiguration()->get('my_extension/enable_some_feature')) {
+            $environment->addBlockStartParser(new MyCustomParser());
+            $environment->addRenderer(MyCustomBlockType::class, new MyCustomRenderer());
+        }
+    }
+}
+
+ +

Within Parsers/Renderers/Listeners

+ +

Perhaps you want to reference those configuration values from within a custom parser, renderer, event listener, or something else. This can easily by done by having that class also implement ConfigurationAwareInterface. This interface signals to the Environment that your class needs a copy of the final configuration so it can read it later:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\Config\ConfigurationAwareInterface;
+use League\Config\ConfigurationInterface;
+
+final class MyCustomRenderer implements NodeRendererInterface, ConfigurationAwareInterface
+{
+    /**
+     * @var ConfigurationInterface
+     */
+    private $config;
+
+    public function setConfiguration(ConfigurationInterface $configuration): void
+    {
+        $this->config = $configuration;
+    }
+
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return 'My favorite number is ' . $this->config->get('my_extension/favorite_number');
+    }
+}
+
+ +

You can access any configuration value from here, not just the ones you might have defined yourself.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/cursor/index.html b/2.2/customization/cursor/index.html new file mode 100644 index 0000000000..f0957ca522 --- /dev/null +++ b/2.2/customization/cursor/index.html @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Parser\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter(int $index)Returns the character at the given absolute position
getCurrentCharacter()Returns the character at the current position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/delimiter-processing/index.html b/2.2/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..65832e1872 --- /dev/null +++ b/2.2/customization/delimiter-processing/index.html @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void;
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
+
+// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
use League\CommonMark\Delimiter\Delimiter;
+use League\CommonMark\Node\Inline\Text;
+
+$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/environment/index.html b/2.2/customization/environment/index.html new file mode 100644 index 0000000000..bf23495d8c --- /dev/null +++ b/2.2/customization/environment/index.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all extensions, parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

An empty Environment can be obtained like this:

+ +
use League\CommonMark\Environment\Environment;
+
+$config = [];
+$environment = new Environment($config);
+
+ +

You can customize the Environment using any of the methods below (from the EnvironmentBuilderInterface interface).

+ +

Once your Environment is configured with whatever configuration and extensions you want, you can instantiate a MarkdownConverter and start converting MD to HTML:

+ +
use League\CommonMark\MarkdownConverter;
+
+// Using $environment from the previous code sample
+$converter = new MarkdownConverter($environment);
+
+echo $converter->convert('# Hello World!');
+
+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. For example, if you want core CommonMark functionality plus footnote support:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+
+$config = [];
+$environment = new Environment($config);
+
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new FootnoteExtension());
+
+ +

addBlockStartParser()

+ +
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockStartParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addRenderer()

+ +
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0);
+
+ +

Registers a NodeRendererInterface to handle a specific type of AST node ($nodeClass) with the given priority (a higher number will be executed earlier).

+ +

See Rendering for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ +

Accessing the Environment and Configuration within parsers/renderers/etc

+ +

If your custom parser/renderer/listener/etc. implements either EnvironmentAwareInterface or ConfigurationAwareInterface we’ll automatically inject the environment or configuration into them once the environment has been fully initialized. This will provide your code with access to the finalized information it may need.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/event-dispatcher/index.html b/2.2/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..12fcb21bbc --- /dev/null +++ b/2.2/customization/event-dispatcher/index.html @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic, PSR-14-compliant event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code.

+ +

Event Class

+ +

Any PSR-14 compliant event can be used, though we also provide an AbstractEvent class you can use to easily create your own events:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - the event object to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

League\CommonMark\Event\DocumentPreRenderEvent

+ +

This event is dispatched by the renderer just before rendering begins. Like with DocumentParsedEvent, this offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering, but with the added knowledge of which format is being rendered to (e.g. html).

+ +

League\CommonMark\Event\DocumentRenderedEvent

+ +

This event is dispatched once the rendering step has been completed, just before the output is returned. The final output can be adjusted at this point or additional metadata can be attached to the return object.

+ +

Bring Your Own PSR-14 Event Dispatcher

+ +

Although this library provides PSR-14 compliant event dispatching out-of-the-box, you may want to use your own PSR-14 event dispatcher instead. This is possible as long as that third-party library both:

+ +
    +
  1. Implements the PSR-14 EventDispatcherInterface; and,
  2. +
  3. Allows you to register additional ListenerProviderInterface instances with that dispatcher library
  4. +
+ +

Not all libraries support this so please check carefully! Assuming yours does, delegating all the event behavior to that library can be done with two steps:

+ +

First, call the setEventDispatcher() method on the Environment to register that other implementation. With that done, any calls to Environment::dispatch() will be passed through to that other dispatcher. But we still need to let that dispatcher know about the events registered by CommonMark extensions, otherwise nothing will happen when events are dispatched.

+ +

Because the Environment implements PSR-14’s ListenerProviderInterface you’ll also need to pass the configured Environment object to your event dispatcher so that it becomes aware of those available events.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event): void
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data->append('attributes/class', 'external-link');
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfiguration()->get('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = new Environment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convert($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/extensions/index.html b/2.2/customization/extensions/index.html new file mode 100644 index 0000000000..2a8bec1c3a --- /dev/null +++ b/2.2/customization/extensions/index.html @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a EnvironmentBuilderInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new EmojiExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/inline-parsing/index.html b/2.2/customization/inline-parsing/index.html new file mode 100644 index 0000000000..b4c4f230e0 --- /dev/null +++ b/2.2/customization/inline-parsing/index.html @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getMatchDefinition()

+ +

This method should return an instance of InlineParserMatch which defines the text the parser is looking for. Examples of this might be something like:

+ +
use League\CommonMark\Parser\Inline\InlineParserMatch;
+
+InlineParserMatch::string('@');                  // Match any '@' characters found in the text
+InlineParserMatch::string('foo');                // Match the text 'foo' (case insensitive)
+
+InlineParserMatch::oneOf('@', '!');              // Match either character
+InlineParserMatch::oneOf('http://', 'https://'); // Match either string
+
+InlineParserMatch::regex('\d+');                 // Match the regular expression (omit the regex delimiters and any flags)
+
+ +

Once a match is found, the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has found at a matching string in the current line; and,
  2. +
  3. No other inline parsers with a higher priority have successfully parsed the text at this point in the line
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser - see more information below.
  • +
+ +
InlineParserContext
+ +

This class has several useful methods:

+ +
    +
  • getContainer() - Returns the current container block the inline text was found in. You’ll almost always call $inlineContext->getContainer()->appendChild(...) to add the parsed inline text inside that block.
  • +
  • getReferenceMap() - Returns the document’s reference map
  • +
  • getCursor() - Returns the current Cursor used to parse the current line. (Note that the cursor will be positioned before the matched text, so you must advance it yourself if you determine it’s a valid match)
  • +
  • getDelimiterStack() - Returns the current delimiter stack. Only used in advanced use cases.
  • +
  • getFullMatch() - Returns the full string that matched you InlineParserMatch definition
  • +
  • getFullMatchLength() - Returns the length of the full match - useful for advancing the cursor
  • +
  • getSubMatches() - If your InlineParserMatch used a regular expression with capture groups, this will return the text matches by those groups.
  • +
  • getMatches() - Returns an array where index 0 is the “full match”, plus any sub-matches. It basically simulates preg_match()’s behavior.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the text at the current position for any reason. Other parsers will then have a chance to try parsing that text. If all registered parsers return false, the text will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed/matched text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::regex('@([A-Za-z0-9_]{1,15}(?!\w))');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+
+        // This seems to be a valid match
+        // Advance the cursor to the end of the match
+        $cursor->advanceBy($inlineContext->getFullMatchLength());
+
+        // Grab the Twitter handle
+        [$handle] = $inlineContext->getSubMatches();
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+// And here's how to hook it up:
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::oneOf(':)', ':(');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($inlineContext->getFullMatch() === ':)') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($inlineContext->getFullMatch() === ':(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance: +
      +
    • Avoid using overly-complex regular expressions in getMatchDefinition() - use the simplest regex you can and have parse() do the heavier validation
    • +
    • Have your parse() method return false as soon as possible.
    • +
    +
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/overview/index.html b/2.2/customization/overview/index.html new file mode 100644 index 0000000000..b870b50ee2 --- /dev/null +++ b/2.2/customization/overview/index.html @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders/configuration you need
  2. +
  3. Instantiate a MarkdownParser and HtmlRenderer using that Environment
  4. +
  5. Use the MarkdownParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  6. +
  7. Use the HtmlRenderer to convert the AST Document into HTML
  8. +
+ +

The MarkdownConverter class handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Renderer\HtmlRenderer;
+
+$environment = new Environment([
+    'html_input' => 'strip',
+]);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderDocument($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renderers convert the parsed blocks/inlines from the AST representation into HTML. When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/rendering/index.html b/2.2/customization/rendering/index.html new file mode 100644 index 0000000000..5fa97bf334 --- /dev/null +++ b/2.2/customization/rendering/index.html @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Custom Rendering

+ +

Renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement NodeRendererInterface and its render() method. Note that in v2.0, both +block renderers and inline renderers share the same interface and method:

+ +

render()

+ +
public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The HtmlRenderer will call this method during the rendering process whenever a supported element is encountered.

+ +

If your renderer can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • Node $node - The encountered block or inline element that needs to be rendered
  • +
  • ChildNodeRendererInterface $childRenderer - If the given $node has children, use this to render those child elements
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the node and its contents, including any children. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\Util\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Renderers

+ +

When registering your renderer, you must tell the Environment which node element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// First param - the node class type that should use our renderer
+// Second param - instance of the renderer
+$environment->addRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple types:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
+use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements NodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(ThematicBreak::class, new TextDividerRenderer());
+
+ +

Note that thematic breaks should not contain children, which is why the $childRenderer is unused in this example. Otherwise we’d have to call code like this and return the result as part of the rendered HTML we’re generating here: $innerHtml = $childRenderer->renderNodes($node->children());

+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any child elements that your node might contain!
  • +
+ +

Wrapping Elements with HtmlDecorator

+ +

A utility class called HtmlDecorator is provided to make it easier to wrap the output of any renderer within an additional HTML tag with custom attributes and/or classes.

+ +

To use it, you register it as a renderer for whatever AST node type you want to wrap:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\Table\TableRenderer;
+use League\CommonMark\Renderer\HtmlDecorator;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new TableExtension());
+$environment->addRenderer(Table::class, new HtmlDecorator(new TableRenderer(), 'div', ['class' => 'table-responsive']));
+
+ +

XML Rendering

+ +

The XML renderer will automatically attempt to convert any AST nodes to XML by inspecting the name of the block/inline node and its attributes. You can instead control the XML element name and attributes by making your renderer implement XmlNodeRendererInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+use League\CommonMark\Xml\XmlNodeRendererInterface;
+
+class TextDividerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+
+    public function getXmlTagName(Node $node): string
+    {
+        return 'text_divider';
+    }
+
+    public function getXmlAttributes(Node $node): array
+    {
+        return ['character' => '='];
+    }
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/customization/slug-normalizer/index.html b/2.2/customization/slug-normalizer/index.html new file mode 100644 index 0000000000..65723c7361 --- /dev/null +++ b/2.2/customization/slug-normalizer/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + Slug Normalizer - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Slug Normalizer

+ +

“Slugs” are strings used within href, name, and id HTML attributes to identify particular elements within a document.

+ +

Some extensions (like the HeadingPermalinkExtension) need the ability to convert user-provided text into these URL-safe slugs while also ensuring that these are unique throughout the generated HTML. The Environment provides a pre-built normalizer you can use for this purpose.

+ +

Usage

+ +

You can obtain a reference to the built-in slug normalizer by calling $environment->getSlugNormalizer();

+ +

To use this within your extension, have your parser/renderer/whatever implement EnvironmentAwareInterface and then implement the corresponding setEnvironment method like this:

+ +

+use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Environment\EnvironmentAwareInterface;
+
+class MyCustomParserOrRenderer implements EnvironmentAwareInterface
+{
+    private $slugNormalizer;
+
+    public function setEnvironment(EnvironmentInterface $environment): void
+    {
+        $this->slugNormalizer = $environment->getSlugNormalizer();
+    }
+}
+
+ +

You can then call $this->slugNormalizer->normalize($text) as needed.

+ +

Configuration

+ +

The slug_normalizer configuration section allows you to adjust the following options:

+ +

instance

+ +

You can change the string that is used as the “slug” by setting the instance option to any class that implements TextNormalizerInterface. +We provide a simple SlugNormalizer by default, but you may want to plug in a different library or create your own normalizer instead.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

max_length

+ +

This can be configured to limit the length of that slug to prevent overly-long values. By default, that limit is 255 characters. You may set this to any positive integer, or 0 for no limit.

+ +

(Note that generated slugs might be slightly longer than this “limit” if the unique option is enabled and the slug generator detects a duplicate slug and needs to add a suffix to make it unique.)

+ +

unique

+ +

This options controls whether slugs should be unique. Possible values include:

+ +
    +
  • 'document' (string; default) - Ensures slugs are unique within a single document
  • +
  • 'environment' (string) - Ensures slugs are unique across multiple documents - see below
  • +
  • false (boolean) - Disables unique slug generation
  • +
+ +

You might have a use case where you’re converting several different Markdown documents on the same page and so you’d like to ensure that none of those documents use conflicting slugs. In that case, you should set the scope option to 'environment' to ensure that a single instance of a MarkdownConverter (which uses a single Environment) will never produce the same slug twice during its lifetime (which usually lasts the entire duration of a single HTTP request).

+ +

If you need complete control over how unique slugs are generated, make your 'instance' implement UniqueSlugNormalizerInterface; otherwise, we’ll simply append incremental numbers to slugs to ensure they are unique.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/attributes/index.html b/2.2/extensions/attributes/index.html new file mode 100644 index 0000000000..0bbe94a50f --- /dev/null +++ b/2.2/extensions/attributes/index.html @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+ +

This results in the following output:

+ +
<blockquote title="Blockquote title">
+<p>A nice blockquote</p>
+</blockquote>
+
+ +

CSS-selector-style declarations can be used to set the id and class attributes:

+ +
{#id .class}
+## Header
+
+ +

Output:

+ +
<h2 class="class" id="id">Header</h2>
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Output:

+ +
<p>This is <em style="color: red">red</em>.</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/autolinks/index.html b/2.2/extensions/autolinks/index.html new file mode 100644 index 0000000000..7ce1e1ad1d --- /dev/null +++ b/2.2/extensions/autolinks/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a Mention Parser outside of this extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/commonmark/index.html b/2.2/extensions/commonmark/index.html new file mode 100644 index 0000000000..8521c90d7b --- /dev/null +++ b/2.2/extensions/commonmark/index.html @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically installed for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new Environment with the core extension
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/default-attributes/index.html b/2.2/extensions/default-attributes/index.html new file mode 100644 index 0000000000..c6dd5ce0a3 --- /dev/null +++ b/2.2/extensions/default-attributes/index.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + Default Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Default Attributes

+ +

The DefaultAttributesExtension allows you to apply default HTML classes and other attributes using configuration options.

+ +

It works by applying the attributes to the nodes during the DocumentParsedEvent event - right after the nodes are parsed but before they are rendered. +(As a result, it’s possible that renderers may add other attributes - the goal of this extension is only to provide custom defaults.)

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DefaultAttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Node\Block\Paragraph;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'default_attributes' => [
+        Heading::class => [
+            'class' => static function (Heading $node) {
+                if ($node->getLevel() === 1) {
+                    return 'title-main';
+                } else {
+                    return null;
+                }
+            },
+        ],
+        Table::class => [
+            'class' => 'table',
+        ],
+        Paragraph::class => [
+            'class' => ['text-center', 'font-comic-sans'],
+        ],
+        Link::class => [
+            'class' => 'btn btn-link',
+            'target' => '_blank',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new DefaultAttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a default_attributes array. Each key in the array should be a FQCN for the node class you wish to apply the default attribute to, and the values should be a map of attribute names to attribute values.

+ +

Attribute values may be any of the following types:

+ +
    +
  • string
  • +
  • string[]
  • +
  • bool
  • +
  • callable (parameter is the Node, return value may be string|string[]|bool)
  • +
+ +

Examples

+ +

Here’s an example that will apply Bootstrap 4 classes and attributes:

+ +
$config = [
+    'default_attributes' => [
+        Table::class => [
+            'class' => ['table', 'table-responsive'],
+        ],
+        BlockQuote::class => [
+            'class' => 'blockquote',
+        ],
+    ],
+];
+
+ +

Here’s a more complex example that uses a callable to add a class only if the paragraph immediately follows an <h1> heading:

+ +
$config = [
+    'default_attributes' => [
+        Paragraph::class => [
+            'class' => static function (Paragraph $paragraph) {
+                if ($paragraph->previous() instanceof Heading && $paragraph->previous()->getLevel() === 1) {
+                    return 'lead';
+                }
+
+                return null;
+            },
+        ],
+    ],
+];
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/description-lists/index.html b/2.2/extensions/description-lists/index.html new file mode 100644 index 0000000000..d3728db3af --- /dev/null +++ b/2.2/extensions/description-lists/index.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + Description List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Description List Extension

+ +

The DescriptionListExtension adds Markdown Extra-style description lists to facilitate the creation of <dl>, <dt>, and <dd> HTML using Markdown.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DescriptionListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\DescriptionList\DescriptionListExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DescriptionListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some markdown goes here');
+
+ +

Syntax

+ +

The syntax is based directly on the rules and logic implemented by the Markdown Extra library. Here are some examples of sample Markdown input and HTML output demonstrating the syntax:

+ +
Apple
+:   Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.
+:   An American computer company.
+
+Orange
+:   The fruit of an evergreen tree of the genus Citrus.
+
+ +
<dl>
+    <dt>Apple</dt>
+    <dd>Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.</dd>
+    <dd>An American computer company.</dd>
+
+    <dt>Orange</dt>
+    <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
+</dl>
+
+ +

See the Markdown Extra documentation or our own spec for additional examples.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/disallowed-raw-html/index.html b/2.2/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..fc6952f415 --- /dev/null +++ b/2.2/extensions/disallowed-raw-html/index.html @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically escapes certain HTML tags when rendering raw HTML, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Customize the extension's configuration if needed
+// Default values are shown below - you can omit this configuration if you're happy with those defaults
+// and don't want to customize them
+$config = [
+    'disallowed_raw_html' => [
+        'disallowed_tags' => ['title', 'textarea', 'style', 'xmp', 'iframe', 'noembed', 'noframes', 'script', 'plaintext'],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I cannot change the page <title>anymore</title>');
+
+ +

Configuration

+ +

This extension can be configured by providing a disallowed_raw_html array with the following nested configuration options. The defaults are shown in the code example above.

+ +

disallowed_tags

+ +

An array containing a list of tags that should be escaped.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/external-links/index.html b/2.2/extensions/external-links/index.html new file mode 100644 index 0000000000..f80da79e75 --- /dev/null +++ b/2.2/extensions/external-links/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class MyCustomLinkRenderer implements NodeRendererInterface
+{
+    /**
+     * @param Node                       $node
+     * @param ChildNodeRendererInterface $childRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        if (!($node instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible node type: ' . \get_class($node));
+        }
+
+        if ($node->data->get('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to automagically add an external link icon by targeting the html_class given in the configuration:

+ +
// Font Awesome example:
+a[target="_blank"]::after,
+a.external::after {
+   content: "\f08e";
+   font: normal normal normal 14px/1 FontAwesome;
+}
+
+// Glyphicon example:
+a[target="_blank"]::after,
+a.external::after {
+  @extend .glyphicon;
+  content: "\e164";
+  margin-left: .5em;
+  margin-right: .25em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/footnotes/index.html b/2.2/extensions/footnotes/index.html new file mode 100644 index 0000000000..399f14c5f8 --- /dev/null +++ b/2.2/extensions/footnotes/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1"></a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'backref_symbol'     => '↩',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

backref_symbol

+ +

This string option sets the symbol used as the contents of the footnote backreference link. It defaults to \League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer::DEFAULT_SYMBOL = '↩'.

+ +

If you want to use a custom icon, set this to an empty string '' and take a look at the Adding Icons section below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ +

Adding Icons

+ +

You can use CSS to add a custom icon instead of providing a backref_symbol:

+ +
$config = [
+    'footnote' => [
+        'backref_class' => 'footnote-backref',
+        'backref_symbol' => '',
+    ],
+];
+
+ +

Then target the backref_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.footnote-backref::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/front-matter/index.html b/2.2/extensions/front-matter/index.html new file mode 100644 index 0000000000..d1d70780c1 --- /dev/null +++ b/2.2/extensions/front-matter/index.html @@ -0,0 +1,542 @@ + + + + + + + + + + + + + + + + + Front Matter Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Front Matter Extension

+ +

The FrontMatterExtension adds the ability to parse YAML front matter from the Markdown document and include that in the return result.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

You will also need to install symfony/yaml or the YAML extension for PHP to use this extension. For symfony/yaml:

+ +
composer require symfony/yaml
+
+ +

(You can use any version of symfony/yaml 2.3 or higher, though we recommend using 4.0 or higher.)

+ +

Front Matter Syntax

+ +

This extension follows the Jekyll Front Matter syntax. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Here is a basic example:

+ +
---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+
+ +

This will produce a front matter array similar to this:

+ +
$parsedFrontMatter = [
+    'layout' => 'post',
+    'title' => 'I Love Markdown',
+    'tags' => [
+        'test',
+        'example',
+    ],
+];
+
+ +

And the HTML output will only contain the one heading:

+ +
<h1>Hello World!</h1>
+
+ +

Usage

+ +

Configure your Environment as usual and add the FrontMatterExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FrontMatterExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+// A sample Markdown file with some front matter:
+$markdown = <<<MD
+---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+MD;
+
+$result = $converter->convert($markdown);
+
+// Grab the front matter:
+if ($result instanceof RenderedContentWithFrontMatter) {
+    $frontMatter = $result->getFrontMatter();
+}
+
+// Output the HTML using any of these:
+echo $result;               // implicit string cast
+// or:
+echo (string) $result;      // explicit string cast
+// or:
+echo $result->getContent();
+
+ +

Parsing Front Matter Only

+ +

You don’t have to parse the entire file (including all the Markdown) if you only want the front matter. You can either instantiate the front matter parser yourself and call it directly, like this:

+ +
use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+// For `symfony/yaml`
+$frontMatterParser = new FrontMatterParser(new SymfonyYamlFrontMatterParser());
+// For YAML extension
+$frontMatterParser = new FrontMatterParser(new LibYamlFrontMatterParser());
+$result = $frontMatterParser->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

Or you can use the getFrontMatterParser() method from the extension:

+ +
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+$frontMatterExtension = new FrontMatterExtension();
+$result = $frontMatterExtension->getFrontMatterParser()->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

This latter approach may be more convenient if you have already instantiated a FrontMatterExtension object you’re adding to the Environment somewhere and just want to call that.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/github-flavored-markdown/index.html b/2.2/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..0ccd5c4816 --- /dev/null +++ b/2.2/extensions/github-flavored-markdown/index.html @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark and GFM parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/heading-permalinks/index.html b/2.2/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..d969879dd4 --- /dev/null +++ b/2.2/extensions/heading-permalinks/index.html @@ -0,0 +1,615 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\MarkdownConverter;
+
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'content',
+        'fragment_prefix' => 'content',
+        'insert' => 'before',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+        'aria_hidden' => true,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

fragment_prefix

+ +

This should be a string you want prepended to the URL fragment in the link’s href attribute. This should typically be set to the same value as id_prefix for links to work properly. However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to “rewrite” them.

+ +

For example, to emulate how GitHub heading permalinks work, set id_prefix to 'user-content', set fragment_prefix to '', and insert some JavaScript into the page like this:

+ +
var scrollToPermalink = function() {
+    var link = document.getElementById('user-content-' + window.location.hash);
+    if (link) {
+        link.scrollIntoView({behavior: 'smooth'});
+    }
+};
+
+window.addEventListener('hashchange', scrollToPermalink);
+if (window.location.hash) {
+    scrollToPermalink();
+}
+
+ +

insert

+ +

This controls whether the anchor is added to the beginning of the <h1>, <h2> etc. tag or to the end. Can be set to either 'before' or 'after'.

+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should have permalinks added. By default, all 6 levels (1, 2, 3, 4, 5, and 6) will have them. You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

aria_hidden

+ +

This option sets the aria-hidden attribute on the <a> tag. This defaults to aria-hidden="true".

+ +

Setting this option to false would render the <a> tag excluding the aria-hidden entirely.

+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/inlines-only/index.html b/2.2/extensions/inlines-only/index.html new file mode 100644 index 0000000000..d9d1363010 --- /dev/null +++ b/2.2/extensions/inlines-only/index.html @@ -0,0 +1,440 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new, empty environment
+$environment = new Environment($config);
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/mentions/index.html b/2.2/extensions/mentions/index.html new file mode 100644 index 0000000000..3e08a04096 --- /dev/null +++ b/2.2/extensions/mentions/index.html @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which prefix you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting prefix, a regular expression to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same prefix, the first mention parser to
+        // successfully match and return a properly constructed Mention object (where the URL has been set) will be the
+        // the mention parser that is used. In this example, the GitHub handle would actually match first because
+        // there isn't any real validation to check whether https://www.github.com/colinodell exists. However, in
+        // CMS applications, you could check whether its a local user first, then check Twitter and then GitHub, etc.
+        'twitter_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[A-Za-z0-9_]{1,15}(?!\w)',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on GitHub: @colinodell');
+// Output:
+// <p>Follow me on GitHub: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Prefix └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the prefix (in this case, @). You can use any prefix, regular expression pattern (without opening/closing delimiter or modifiers), or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Node\Inline\AbstractInline;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Inline\Element\AbstractInline;
+
+class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'user_url_generator' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z0-9]+',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/overview/index.html b/2.2/extensions/overview/index.html new file mode 100644 index 0000000000..9a300669f2 --- /dev/null +++ b/2.2/extensions/overview/index.html @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Default AttributesEasily apply default HTML classes using configuration options to match your site’s styles2.0.0 
Description ListsCreate <dl> description lists using Markdown Extra’s syntax2.0.0 
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
Front MatterParses YAML front matter from your Markdown input2.0.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the extensions you need
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the other extensions you need
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/smart-punctuation/index.html b/2.2/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..9bff886f36 --- /dev/null +++ b/2.2/extensions/smart-punctuation/index.html @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/strikethrough/index.html b/2.2/extensions/strikethrough/index.html new file mode 100644 index 0000000000..670e9fed16 --- /dev/null +++ b/2.2/extensions/strikethrough/index.html @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/table-of-contents/index.html b/2.2/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..10aeaae62e --- /dev/null +++ b/2.2/extensions/table-of-contents/index.html @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/tables/index.html b/2.2/extensions/tables/index.html new file mode 100644 index 0000000000..6e7f37cd61 --- /dev/null +++ b/2.2/extensions/tables/index.html @@ -0,0 +1,522 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => false,
+            'tag' => 'div',
+            'attributes' => [],
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Configuration

+ +

Wrapping Container

+ +

You can “wrap” the table with a container element by configuring the following options:

+ +
    +
  • enabled: (boolean) Whether to wrap the table with a container element. Defaults to false.
  • +
  • tag: (string) The tag name of the container element. Defaults to div.
  • +
  • attributes: (array) An array of attributes to apply to the container element. Defaults to [].
  • +
+ +

For example, to wrap all tables within a <div class="table-responsive"> container element:

+ +
$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => true,
+            'tag' => 'div',
+            'attributes' => ['class' => 'table-responsive'],
+        ],
+    ],
+];
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/extensions/task-lists/index.html b/2.2/extensions/task-lists/index.html new file mode 100644 index 0000000000..372b4dac17 --- /dev/null +++ b/2.2/extensions/task-lists/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convert($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/index.html b/2.2/index.html new file mode 100644 index 0000000000..5b92ea1cba --- /dev/null +++ b/2.2/index.html @@ -0,0 +1,443 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello, World!')->getContent();
+
+// <h1>Hello, World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/installation/index.html b/2.2/installation/index.html new file mode 100644 index 0000000000..9cf612f7ce --- /dev/null +++ b/2.2/installation/index.html @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +
composer require "league/commonmark:^2.2"
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^2.2. This is equivalent to >=2.2 <3.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/security/index.html b/2.2/security/index.html new file mode 100644 index 0000000000..98a43396f8 --- /dev/null +++ b/2.2/security/index.html @@ -0,0 +1,493 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convert($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/support/index.html b/2.2/support/index.html new file mode 100644 index 0000000000..f8b32c4765 --- /dev/null +++ b/2.2/support/index.html @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + Support - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Support

+ +

Here are some useful resources to help you use this project:

+ + + +

Supported Versions

+ +

See our security policy for information about the support cycle for bug fixes and security updates.

+ +

Reporting a Vulnerability

+ +

If you discover a security vulnerability within this package, please use the Tidelift security contact form or email Colin O’Dell at colinodell@gmail.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/upgrading/index.html b/2.2/upgrading/index.html new file mode 100644 index 0000000000..aa463c4fb3 --- /dev/null +++ b/2.2/upgrading/index.html @@ -0,0 +1,412 @@ + + + + + + + + + + + + + + + + + Upgrading from 2.1 to 2.2 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 2.1 to 2.2

+ +

Deprecation of MarkdownConverterInterface

+ +

The MarkdownConverterInterface and its convertToHtml() method were deprecated in 2.2.0 and will be removed in 3.0.0. +You should switch your implementations to use ConverterInterface and convert() instead which provide the same behavior.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.2/xml/index.html b/2.2/xml/index.html new file mode 100644 index 0000000000..bf32b96374 --- /dev/null +++ b/2.2/xml/index.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + XML Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.2. Please consider upgrading your code to the latest stable version

+ + +

XML Rendering

+ +

Version 2.0 introduced the ability to render Markdown Document objects in XML. This is particularly useful for debugging custom extensions as you can see the XML representation of the Abstract Syntax Tree.

+ +

To convert Markdown to XML, you would instantiate a MarkdownToXmlConverter with an Environment and then call convert() on any Markdown.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Xml\MarkdownToXmlConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$converter = new MarkdownToXmlConverter($environment);
+
+echo $converter->convert('# **Hello** World!');
+
+ +

This will display XML output like this:

+ +
<?xml version="1.0" encoding="UTF-8"?>
+<document xmlns="http://commonmark.org/xml/1.0">
+    <heading level="1">
+        <strong>
+            <text>Hello</text>
+        </strong>
+        <text> World!</text>
+    </heading>
+</document>
+
+ +

Alternatively, if you already have a Document object you want to visualize in XML, you can use theXmlRenderer class to convert it to XML.

+ +

Return Value

+ +

Like with CommonMarkConverter::convert(), the renderDocument() actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final XML output.

+ +

Customizing the XML Output

+ +

See the rendering documentation for information on customizing the XML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/basic-usage/index.html b/2.3/basic-usage/index.html new file mode 100644 index 0000000000..6b5aa2cbb0 --- /dev/null +++ b/2.3/basic-usage/index.html @@ -0,0 +1,500 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Basic Usage

+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Using Extensions

+ +

The CommonMarkConverter and GithubFlavoredMarkdownConverter shown above automatically configure the environment for you, but if you want to use additional extensions you’ll need to avoid those classes and use the generic MarkdownConverter class instead to customize the environment with whatever extensions you wish to use:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+$environment->addExtension(new InlinesOnlyExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+// <p><strong>Hello World!</strong></p>
+
+ +

Configuration

+ +

If you’re using the CommonMarkConverter or GithubFlavoredMarkdownConverter class you can pass configuration options directly into their constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

See the configuration section for more information on the available configuration options.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ +

Return Value

+ +

The convert() method actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final HTML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/changelog/index.html b/2.3/changelog/index.html new file mode 100644 index 0000000000..a3acb61f5d --- /dev/null +++ b/2.3/changelog/index.html @@ -0,0 +1,722 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Changelog

+ +

All notable changes made in 2.x releases are shown below. See the full list of releases for the complete changelog.

+ +

2.5.3 - 2024-08-16

+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.1, including: +
      +
    • Remove source, add search to list of recognized block tags
    • +
    +
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.2…2.5.3

+ +

2.5.2 - 2024-08-14

+ +

Changed

+ +
    +
  • Boolean attributes now require an explicit true value (#1040)
  • +
+ +

Fixed

+ +
    +
  • Fixed regression where text could be misinterpreted as an attribute (#1040)
  • +
+ +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.1…2.5.2

+ +

2.5.1 - 2024-07-24

+ +

Fixed

+ +
    +
  • Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
  • +
  • Fixed incorrect Table start line numbers (#1037)
  • +
+ +

New Contributors

+
    +
  • @jasonvarga made their first contribution in https://github.com/thephpleague/commonmark/pull/1035
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.0…2.5.1

+ +

2.5.0 - 2024-07-22

+ +

Added

+ +
    +
  • The AttributesExtension now supports attributes without values (#985, #986)
  • +
  • The AutolinkExtension exposes two new configuration options to override the default behavior (#969, #987): +
      +
    • autolink/allowed_protocols - an array of protocols to allow autolinking for
    • +
    • autolink/default_protocol - the default protocol to use when none is specified
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.0, including: +
      +
    • Allow closing fence to be followed by tabs
    • +
    • Remove restrictive limitation on inline comments
    • +
    • Unicode symbols now treated like punctuation (for purposes of flankingness)
    • +
    • Trailing tabs on the last line of indented code blocks will be excluded
    • +
    • Improved HTML comment matching
    • +
    +
  • +
  • Paragraphs only containing link reference definitions will be kept in the AST until the Document is finalized +
      +
    • (These were previously removed immediately after parsing the Paragraph)
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed list tightness not being determined properly in some edge cases
  • +
  • Fixed incorrect ending line numbers for several block types in various scenarios
  • +
  • Fixed lowercase inline HTML declarations not being accepted
  • +
+ +

New Contributors

+
    +
  • @svenluijten made their first contribution in https://github.com/thephpleague/commonmark/pull/986
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.4…2.5.0

+ +

2.4.4 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.3…2.4.4

+ +

2.4.3 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
  • +
  • Fixed UrlAutolinkParser incorrectly parsing text containing www anywhere before an autolink (#1025)
  • +
+ +

New Contributors

+
    +
  • @nfreader made their first contribution in https://github.com/thephpleague/commonmark/pull/1016
  • +
  • @macbookandrew made their first contribution in https://github.com/thephpleague/commonmark/pull/1025
  • +
  • @xavierlacot made their first contribution in https://github.com/thephpleague/commonmark/pull/1013
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.2…2.4.3

+ +

2.4.2 - 2024-02-02

+ +

Fixed

+ +
    +
  • Fixed declaration parser being too strict
  • +
  • FencedCodeRenderer: don’t add language- to class if already prefixed
  • +
+ +

New Contributors

+
    +
  • @sergiy-petrov made their first contribution in https://github.com/thephpleague/commonmark/pull/997
  • +
  • @clustermin made their first contribution in https://github.com/thephpleague/commonmark/pull/988
  • +
  • @peter279k made their first contribution in https://github.com/thephpleague/commonmark/pull/996
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.1…2.4.2

+ +

2.4.1 - 2023-08-30

+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not fully disabling the rel attribute when configured to do so (#992)
  • +
+ +

2.4.0 - 2023-03-24

+ +

See the upgrading guide for more information about the exception-related changes

+ +

Added

+ +
    +
  • Added generic CommonMarkException marker interface for all exceptions thrown by the library
  • +
  • Added several new specific exception types implementing that marker interface: +
      +
    • AlreadyInitializedException
    • +
    • InvalidArgumentException
    • +
    • IOException
    • +
    • LogicException
    • +
    • MissingDependencyException
    • +
    • NoMatchingRendererException
    • +
    • ParserLogicException
    • +
    +
  • +
  • Added more configuration options to the Heading Permalinks extension (#939): +
      +
    • heading_permalink/apply_id_to_heading - When true, the id attribute will be applied to the heading element itself instead of the <a> tag
    • +
    • heading_permalink/heading_class - class to apply to the heading element
    • +
    • heading_permalink/insert - now accepts none to prevent the creation of the <a> link
    • +
    +
  • +
  • Added new table/alignment_attributes configuration option to control how table cell alignment is rendered (#959)
  • +
+ +

Changed

+ +
    +
  • Change several thrown exceptions from RuntimeException to LogicException (or something extending it), including: +
      +
    • CallbackGenerators that fail to set a URL or return an expected value
    • +
    • MarkdownParser when deactivating the last block parser or attempting to get an active block parser when they’ve all been closed
    • +
    • Adding items to an already-initialized Environment
    • +
    • Rendering a Node when no renderer has been registered for it
    • +
    +
  • +
  • HeadingPermalinkProcessor now throws InvalidConfigurationException instead of RuntimeException when invalid config values are given.
  • +
  • HtmlElement::setAttribute() no longer requires the second parameter for boolean attributes
  • +
  • Several small micro-optimizations
  • +
  • Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
  • +
+ +

Fixed

+ +
    +
  • Fixed inaccurate @throws docblocks throughout the codebase, including ConverterInterface, MarkdownConverter, and MarkdownConverterInterface. +
      +
    • These previously suggested that only \RuntimeExceptions were thrown, which was inaccurate as \LogicExceptions were also possible.
    • +
    +
  • +
+ +

2.3.9 - 2023-02-15

+ +

Fixed

+ +
    +
  • Fixed autolink extension not detecting some URIs with underscores (#956)
  • +
+ +

2.3.8 - 2022-12-10

+ +

Fixed

+ +
    +
  • Fixed parsing issues when mb_internal_encoding() is set to something other than UTF-8 (#951)
  • +
+ +

2.3.7 - 2022-11-17

+ +

Fixed

+ +
    +
  • Fixed TaskListItemMarkerRenderer not including HTML attributes set on the node by other extensions (#947)
  • +
+ +

2.3.6 - 2022-10-30

+ +

Fixed

+ +
    +
  • Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a .) (#943)
  • +
+ +

2.3.5 - 2022-07-29

+ +

Fixed

+ +
    +
  • Fixed error using InlineParserEngine when no inline parsers are registered in the Environment (#908)
  • +
+ +

2.3.4 - 2022-07-17

+ +

Changed

+ +
    +
  • Made a number of small tweaks to the embed extension’s parsing behavior to fix #898: +
      +
    • Changed EmbedStartParser to always capture embed-like lines in container blocks, regardless of parent block type
    • +
    • Changed EmbedProcessor to also remove Embed blocks that aren’t direct children of the Document
    • +
    • Increased the priority of EmbedProcessor to 1010
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed EmbedExtension not parsing embeds following a list block (#898)
  • +
+ +

2.3.3 - 2022-06-07

+ +

Fixed

+ +
    +
  • Fixed DomainFilteringAdapter not reindexing the embed list (#884, #885)
  • +
+ +

2.3.2 - 2022-06-03

+ +

Fixed

+ +
    +
  • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
  • +
+ +

2.3.1 - 2022-05-14

+ +

Fixed

+ +
    +
  • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
  • +
+ +

2.3.0 - 2022-04-07

+ +

Added

+ +
    +
  • Added new EmbedExtension (#805)
  • +
  • Added DocumentRendererInterface as a replacement for the now-deprecated MarkdownRendererInterface
  • +
+ +

Deprecated

+ +
    +
  • Deprecated MarkdownRendererInterface; use DocumentRendererInterface instead
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/configuration/index.html b/2.3/configuration/index.html new file mode 100644 index 0000000000..b3b1708e7a --- /dev/null +++ b/2.3/configuration/index.html @@ -0,0 +1,511 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Configuration

+ +

Many aspects of this library’s behavior can be tweaked using configuration options.

+ +

You can provide an array of configuration options to the Environment or converter classes when creating them:

+ +
$config = [
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'commonmark' => [
+        'enable_em' => true,
+        'enable_strong' => true,
+        'use_asterisk' => true,
+        'use_underscore' => true,
+        'unordered_list_markers' => ['-', '*', '+'],
+    ],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => PHP_INT_MAX,
+    'slug_normalizer' => [
+        'max_length' => 255,
+    ],
+];
+
+ +

If you’re using the basic CommonMarkConverter or GithubFlavoredMarkdown classes, simply pass the configuration array into the constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

Here’s a list of the core configuration options available:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: PHP_INT_MAX). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested.
  • +
  • slug_normalizer - Array of options for configuring how URL-safe slugs are created; see the slug normalizer docs for more details +
      +
    • instance - An alternative normalizer to use (defaults to the included SlugNormalizer)
    • +
    • max_length - Limits the size of generated slugs (defaults to 255 characters)
    • +
    • unique - Controls whether slugs should be unique per 'document' (default) or per 'environment'; can be disabled with false
    • +
    +
  • +
+ +

Additional configuration options are available for most of the available extensions - refer to their individual documentation for more details. For example, the CommonMark core extension offers these additional options:

+ +
    +
  • commonmark - Array of options for configuring the CommonMark core extension: +
      +
    • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
    • +
    • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
    • +
    • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
    • +
    • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
    • +
    • unordered_list_markers - Array of characters that can be used to indicate a bulleted list (default: ["-", "*", "+"])
    • +
    +
  • +
+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/abstract-syntax-tree/index.html b/2.3/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..6b5e972f19 --- /dev/null +++ b/2.3/customization/abstract-syntax-tree/index.html @@ -0,0 +1,706 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the MarkdownParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent see the (Event Dispatcher documentation)
  • +
+ +

Visualization

+ +

Even with an interactive debugger it can be tricky to view an entire tree at once. Consider using the XmlRenderer to provide a simple text-based representation of the AST for debugging purposes.

+ +

Node Traversal

+ +

There are four different ways to traverse/iterate the Nodes within the AST:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodProsCons
Manual TraversalBest way to access/check direct relatives of nodesNot useful for iteration
Iterating the TreeFast and efficientPossible unexpected behavior when adding/removing sibling nodes while iterating
Walking the TreeFull control over iterationUp to twice as slow as iteration; adding/removing nodes while iterating can lead to weird behaviors
Querying NodesEasier to write and understand; no weird behaviorsNot memory efficient
+ +

Each is described in more detail below

+ +

Manual Traversal

+ +

The following methods can be used to manually traverse from one Node to any of its direct relatives:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

This is best suited for situations when you need to know information about those relatives.

+ +

Iterating the Tree

+ +

If you’d like to iterate through all the nodes, use the iterator() method to obtain an iterator that will loop through each node in the tree (using pre-order traversal):

+ +
foreach ($document->iterator() as $node) {
+    echo 'Current node: ' . get_class($node) . "\n";
+}
+
+ +

Given an AST like this (XML representation):

+ +
<document>
+  <heading level="1">
+    <text>Hello World!</text>
+  </heading>
+  <paragraph>
+    <text>This is an example of </text>
+    <strong>
+      <text>CommonMark</text>
+    </strong>
+    <text>.</text>
+  </paragraph>
+</document>
+
+ +

The code above will output:

+ +
Current node: League\CommonMark\Node\Block\Document
+Current node: League\CommonMark\Extension\CommonMark\Node\Block\Heading
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Block\Paragraph
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Extension\CommonMark\Node\Inline\Strong
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Inline\Text
+
+ +

This iterator doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes. It’s also very CPU and memory-efficient.

+ +

Be careful when modifying nodes while iterating the tree as some of those changes may affect the current iteration process, especially for sibling nodes that come after the current one. For example, if you remove the current node’s next() sibling, the next loop of that iteration will still include the removed sibling even though it was successfully removed from the AST. Similarly, any new siblings that are added won’t be visited on the next loop.

+ +

Walking the Tree

+ +

If you’d like to walk through all the nodes, visiting each one as you enter and leave it, use the walker() method to obtain an instance of NodeWalker. This also uses pre-order traversal but emitting NodeWalkerEvents along the way:

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'Now ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

Using the same example AST in the previous section, this code will output:

+ +
Now entering a League\CommonMark\Node\Block\Document node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Block\Paragraph node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Node\Block\Paragraph node
+Now leaving a League\CommonMark\Node\Block\Document node
+
+ +

This approach offers many of the same benefits as the simple iteration shown in the previous section such as memory efficiency and no recursion. The key differences come from how you enter and leave nodes:

+ +
    +
  1. Iteration can potentially take twice as long - not ideal for performance
  2. +
  3. Provides you with more control over exactly when an action is taken on a node which is sometimes needed for certain AST manipulations
  4. +
  5. Also provides a resumeAt() method to override where it should iterate next
  6. +
+ +

But like with the iterator, be careful when adding/removing nodes while walking the tree, as there are even more subtle cases where the walker could even lose track of where it was, which may result in some nodes being visited multiple times or not at all.

+ +

Querying Nodes

+ +

If you’re trying to locate certain nodes to perform actions on them, querying the nodes from the AST might be easier to implement. This can be done with the Query class:

+ +
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Node\Block\Paragraph;
+use League\CommonMark\Node\Query;
+
+// Find all paragraphs and blockquotes that contain links
+$matchingNodes = (new Query())
+    ->where(Query::type(Paragraph::class))
+    ->orWhere(Query::type(BlockQuote::class))
+    ->andWhere(Query::hasChild(Query::type(Link::class)))
+    ->findAll($document);
+
+foreach ($matchingNodes as $node) {
+    // TODO: Do something with them
+}
+
+ +

Each condition passed into where(), orWhere(), or andWhere() must be a callable “filter” that accepts a Node and returns true or false. We provide several methods that can help create these filters for you:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Query::type(string $class)Creates a filter that matches nodes with the given class name
Query::hasChild()Creates a filter that matches nodes which contain at least one child
Query::hasChild(callable $condition)Creates a filter that matches nodes which contain at least one child that matches the inner $condition
Query::hasParent()Creates a filter that matches nodes which have a parent
Query::hasParent(callable $condition)Creates a filter that matches nodes which have a parent that matches the inner $condition
+ +

You can of course create your own custom filters/conditions using an anonymous function or by implementing ExpressionInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Node\Query;
+use League\CommonMark\Node\Query\ExpressionInterface;
+
+class ChildCountGreaterThan implements ExpressionInterface
+{
+    private $count;
+
+    public function __construct(int $count)
+    {
+        $this->count = $count;
+    }
+
+    public function __invoke(Node $node) : bool{
+        return count($node->children()) > $this->count;
+    }
+}
+
+$query = (new Query())
+    ->where(function (Node $node): bool { return $node->data->has('attributes/class'); })
+    ->andWhere(new ChildCountGreaterThan(3));
+
+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ +

Data Storage

+ +

Each Node has a property called data which is a Data (array-like) object. This can be used to store any arbitrary data you’d like on the node:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text1 = new Text('Hello, world!');
+$text1->data->set('language', 'English');
+$text1->data->set('is_good_translation', true);
+
+$text2 = new Text('Bonjour monde!');
+$text2->data->set('language', 'French');
+$text2->data->set('is_good_translation', false);
+
+foreach ([$text1, $text2] as $text) {
+    if ($text->data->get('is_good_translation')) {
+        sprintf('In %s we would say: "%s"', $text->data->get('language'), $text->getLiteral());
+    } else {
+        sprintf('I think they would say "%s" in %s, but I\'m not sure.', $text->getLiteral(), $text->data->get('language'));
+    }
+}
+
+ +

You can also access deeply-nested paths using / or . as delimiters:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text = new Text('Hello, world!');
+$text->data->set('info', ['language' => 'English', 'is_good_translation' => true]);
+
+var_dump($text->data->get('info/language'));
+var_dump($text->data->get('info.is_good_translation'));
+
+$text->data->set('info/is_example', true);
+
+ +

HTML Attributes

+ +

The data property comes pre-instantiated with a single data element called attributes which is used to store any HTML attributes that need to be rendered. For example:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+$link = new Link('https://twitter.com/colinodell', '@colinodell');
+$link->data->append('attributes/class', 'social-link');
+$link->data->append('attributes/class', 'twitter');
+$link->data->set('attributes/target', '_blank');
+$link->data->set('attributes/rel', 'noopener');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/block-parsing/index.html b/2.3/customization/block-parsing/index.html new file mode 100644 index 0000000000..2270bfc55d --- /dev/null +++ b/2.3/customization/block-parsing/index.html @@ -0,0 +1,550 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Block Parsing

+ +

At a high level, block parsing is a two-step process:

+ +
    +
  1. Using a BlockStartParserInterface to identify if/where a block start exists on the given line
  2. +
  3. Using a BlockContinueParserInterface to perform additional processing of the identified block
  4. +
+ +

So to implement a custom block parser you will actually need to implement both of these classes.

+ +

BlockStartParserInterface

+ +

Instances of this interface have a single tryStart() method:

+ +
/**
+ * Check whether we should handle the block at the current position
+ *
+ * @param Cursor                       $cursor
+ * @param MarkdownParserStateInterface $parserState
+ *
+ * @return BlockStart|null
+ */
+public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart;
+
+ +

Given a Cursor at the current position, plus some extra information about the state of the parser, this method is responsible for determining whether a particular type of block seems to exist at the given position. You don’t actually parse the block here - that’s the job of a BlockContinueParserInterface. Your only job here is to return whether or not a particular type of block does exist here, and if so which block parser should parse it.

+ +

If you find that you cannot parse the given block, you should return BlockStart::none(); from this function.

+ +

However, if the Markdown at the current position does indeed seem to be the type of block you’re looking for, you should return a BlockStart instance using the following static constructor pattern:

+ +
use League\CommonMark\Parser\Block\BlockStart;
+
+return BlockStart::of(new MyCustomParser())->at($cursor);
+
+ +

Unlike in 1.x, the Cursor state is no longer shared between parsers. You must therefore explicitly provide the BlockStart object with a copy of your cursor at the correct, post-parsing position.

+ +

NOTE: If your custom block starts with a letter character you’ll need to add your parser to the environment with a priority of 250 or higher. This is due to a performance optimization where such lines are usually skipped.

+ +

BlockContinueParserInterface

+ +

The previous interface only helps the engine identify where a block starts. Additional information about the block, as well as the ability to parse additional lines of input, is all handled by the BlockContinueParserInterface.

+ +

This interface has several methods, so it’s usually easier to extend from AbstractBlockContinueParser instead, which sets most of the methods to use typical defaults you can override as needed.

+ +

getBlock()

+ +
public function getBlock(): AbstractBlock;
+
+ +

Each instance of a BlockContinueParserInterface is associated with a new block that is being parsed. This method here returns that block.

+ +

isContainer()

+ +
public function isContainer(): bool;
+
+ +

This method returns whether or not the block is a “container” capable of containing other blocks as children.

+ +

canContain()

+ +
public function canContain(AbstractBlock $childBlock): bool;
+
+ +

This method returns whether the current block being parsed can contain the given child block.

+ +

canHaveLazyContinuationLines()

+ +
public function canHaveLazyContinuationLines(): bool;
+
+ +

This method returns whether or not this parser should also receive subsequent lines of Markdown input. This is primarily used when a block can span multiple lines, like code blocks do.

+ +

addLine()

+ +
public function addLine(string $line): void;
+
+ +

If canHaveLazyContinuationLines() returned true, this method will be called with the additional lines of content.

+ +

tryContinue()

+ +
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue;
+
+ +

This method allows you to try and parse an additional line of Markdown.

+ +

closeBlock()

+ +
public function closeBlock(): void;
+
+ +

This method is called when the block is done being parsed. Any final adjustments to the block should be made at this time.

+ +

parseInlines()

+ +
public function parseInlines(InlineParserEngineInterface $inlineParser): void;
+
+ +

This method is called when the engine is ready to parse any inline child elements.

+ +

Note: For performance reasons, this method is not part of BlockContinueParserInterface. If your block may contain inlines, you should make sure that your “continue parser” also implements BlockContinueParserWithInlinesInterface.

+ +

Tips

+ +

Here are some additional tips to consider when writing your own custom parsers:

+ +

Combining both into one file

+ +

Although parsing requires two classes, you can use the anonymous class feature of PHP to combine both into a single file! Here’s an example:

+ +
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
+use League\CommonMark\Parser\Block\BlockStartParserInterface;
+
+final class MyCustomBlockParser extends AbstractBlockContinueParser
+{
+    // TODO: implement your continuation parsing methods here
+
+    public static function createBlockStartParser(): BlockStartParserInterface
+    {
+        return new class implements BlockStartParserInterface
+        {
+            // TODO: implement the tryStart() method here
+        };
+    }
+}
+
+
+ +

Performance

+ +

The BlockStartParserInterface::tryStart() and BlockContinueParserInterface::tryContinue() methods may be called hundreds or thousands of times during execution. For best performance, have your methods return as early as possible, and make sure your code is highly optimized.

+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

If your block contains literal strings/text within the block (and not as part of a child block), you should have your custom block type also implement StringContainerInterface.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/configuration/index.html b/2.3/customization/configuration/index.html new file mode 100644 index 0000000000..abdbf143be --- /dev/null +++ b/2.3/customization/configuration/index.html @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Configuration Schemas and Values

+ +

Version 2.0 introduced a new robust system for defining configuration schemas and accessing them within custom extensions.

+ +

Configuration Schemas

+ +

Unlike in 1.x, all configuration options must have a defined schema. This defines which options are available, what types of values they accept, whether any are required, and any default values you wish to define if the user doesn’t provide any.

+ +

These custom options can be defined from within your custom extension by implementing the ConfigurableExtensionInterface:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+use Nette\Schema\Expect;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        $builder->addSchema('my_extension', Expect::structure([
+            'enable_some_feature' => Expect::bool()->default(true),
+            'html_class' => Expect::string()->default('my-custom-extension'),
+            'align' => Expect::anyOf('left', 'center', 'right')->default('left'),
+            'favorite_number' => Expect::int()->min(1)->max(100)->default(42),
+        ]));
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        // TODO: Implement register() method
+    }
+}
+
+ +

See the league/config documentation for more examples of how to define custom configuration schemas.

+ +

Note that you only need to implement ConfigurableExtensionInterface if you plan to define new configuration options - you don’t need this if you’re only reading existing options.

+ +

Reading Configuration Values

+ +

Okay, so your extension has defined the different options that are available, but now you want to start using them within your custom extension. There are a few ways you can access the values:

+ +

During Extension Registration

+ +

Perhaps your extension needs to decide whether/how to register certain parsers/renderers/etc based on the user-provided configuration values - in that case, you can read the value from the $environment - for example:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        // (see code example above)
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        if ($environment->getConfiguration()->get('my_extension/enable_some_feature')) {
+            $environment->addBlockStartParser(new MyCustomParser());
+            $environment->addRenderer(MyCustomBlockType::class, new MyCustomRenderer());
+        }
+    }
+}
+
+ +

Within Parsers/Renderers/Listeners

+ +

Perhaps you want to reference those configuration values from within a custom parser, renderer, event listener, or something else. This can easily by done by having that class also implement ConfigurationAwareInterface. This interface signals to the Environment that your class needs a copy of the final configuration so it can read it later:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\Config\ConfigurationAwareInterface;
+use League\Config\ConfigurationInterface;
+
+final class MyCustomRenderer implements NodeRendererInterface, ConfigurationAwareInterface
+{
+    /**
+     * @var ConfigurationInterface
+     */
+    private $config;
+
+    public function setConfiguration(ConfigurationInterface $configuration): void
+    {
+        $this->config = $configuration;
+    }
+
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return 'My favorite number is ' . $this->config->get('my_extension/favorite_number');
+    }
+}
+
+ +

You can access any configuration value from here, not just the ones you might have defined yourself.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/cursor/index.html b/2.3/customization/cursor/index.html new file mode 100644 index 0000000000..86e66262a5 --- /dev/null +++ b/2.3/customization/cursor/index.html @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Parser\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter(int $index)Returns the character at the given absolute position
getCurrentCharacter()Returns the character at the current position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/delimiter-processing/index.html b/2.3/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..1d193fb119 --- /dev/null +++ b/2.3/customization/delimiter-processing/index.html @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void;
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
+
+// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
use League\CommonMark\Delimiter\Delimiter;
+use League\CommonMark\Node\Inline\Text;
+
+$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/environment/index.html b/2.3/customization/environment/index.html new file mode 100644 index 0000000000..90cf2262a5 --- /dev/null +++ b/2.3/customization/environment/index.html @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all extensions, parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

An empty Environment can be obtained like this:

+ +
use League\CommonMark\Environment\Environment;
+
+$config = [];
+$environment = new Environment($config);
+
+ +

You can customize the Environment using any of the methods below (from the EnvironmentBuilderInterface interface).

+ +

Once your Environment is configured with whatever configuration and extensions you want, you can instantiate a MarkdownConverter and start converting MD to HTML:

+ +
use League\CommonMark\MarkdownConverter;
+
+// Using $environment from the previous code sample
+$converter = new MarkdownConverter($environment);
+
+echo $converter->convert('# Hello World!');
+
+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. For example, if you want core CommonMark functionality plus footnote support:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+
+$config = [];
+$environment = new Environment($config);
+
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new FootnoteExtension());
+
+ +

addBlockStartParser()

+ +
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockStartParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addRenderer()

+ +
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0);
+
+ +

Registers a NodeRendererInterface to handle a specific type of AST node ($nodeClass) with the given priority (a higher number will be executed earlier).

+ +

See Rendering for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ +

Accessing the Environment and Configuration within parsers/renderers/etc

+ +

If your custom parser/renderer/listener/etc. implements either EnvironmentAwareInterface or ConfigurationAwareInterface we’ll automatically inject the environment or configuration into them once the environment has been fully initialized. This will provide your code with access to the finalized information it may need.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/event-dispatcher/index.html b/2.3/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..772614290b --- /dev/null +++ b/2.3/customization/event-dispatcher/index.html @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Event Dispatcher

+ +

This library includes basic, PSR-14-compliant event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code.

+ +

Event Class

+ +

Any PSR-14 compliant event can be used, though we also provide an AbstractEvent class you can use to easily create your own events:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - the event object to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

League\CommonMark\Event\DocumentPreRenderEvent

+ +

This event is dispatched by the renderer just before rendering begins. Like with DocumentParsedEvent, this offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering, but with the added knowledge of which format is being rendered to (e.g. html).

+ +

League\CommonMark\Event\DocumentRenderedEvent

+ +

This event is dispatched once the rendering step has been completed, just before the output is returned. The final output can be adjusted at this point or additional metadata can be attached to the return object.

+ +

Bring Your Own PSR-14 Event Dispatcher

+ +

Although this library provides PSR-14 compliant event dispatching out-of-the-box, you may want to use your own PSR-14 event dispatcher instead. This is possible as long as that third-party library both:

+ +
    +
  1. Implements the PSR-14 EventDispatcherInterface; and,
  2. +
  3. Allows you to register additional ListenerProviderInterface instances with that dispatcher library
  4. +
+ +

Not all libraries support this so please check carefully! Assuming yours does, delegating all the event behavior to that library can be done with two steps:

+ +

First, call the setEventDispatcher() method on the Environment to register that other implementation. With that done, any calls to Environment::dispatch() will be passed through to that other dispatcher. But we still need to let that dispatcher know about the events registered by CommonMark extensions, otherwise nothing will happen when events are dispatched.

+ +

Because the Environment implements PSR-14’s ListenerProviderInterface you’ll also need to pass the configured Environment object to your event dispatcher so that it becomes aware of those available events.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event): void
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data->append('attributes/class', 'external-link');
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfiguration()->get('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = new Environment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convert($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/extensions/index.html b/2.3/customization/extensions/index.html new file mode 100644 index 0000000000..b09277f809 --- /dev/null +++ b/2.3/customization/extensions/index.html @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a EnvironmentBuilderInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new EmojiExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/inline-parsing/index.html b/2.3/customization/inline-parsing/index.html new file mode 100644 index 0000000000..5c5f40bde7 --- /dev/null +++ b/2.3/customization/inline-parsing/index.html @@ -0,0 +1,596 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getMatchDefinition()

+ +

This method should return an instance of InlineParserMatch which defines the text the parser is looking for. Examples of this might be something like:

+ +
use League\CommonMark\Parser\Inline\InlineParserMatch;
+
+InlineParserMatch::string('@');                  // Match any '@' characters found in the text
+InlineParserMatch::string('foo');                // Match the text 'foo' (case insensitive)
+
+InlineParserMatch::oneOf('@', '!');              // Match either character
+InlineParserMatch::oneOf('http://', 'https://'); // Match either string
+
+InlineParserMatch::regex('\d+');                 // Match the regular expression (omit the regex delimiters and any flags)
+
+ +

Once a match is found, the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has found at a matching string in the current line; and,
  2. +
  3. No other inline parsers with a higher priority have successfully parsed the text at this point in the line
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser - see more information below.
  • +
+ +
InlineParserContext
+ +

This class has several useful methods:

+ +
    +
  • getContainer() - Returns the current container block the inline text was found in. You’ll almost always call $inlineContext->getContainer()->appendChild(...) to add the parsed inline text inside that block.
  • +
  • getReferenceMap() - Returns the document’s reference map
  • +
  • getCursor() - Returns the current Cursor used to parse the current line. (Note that the cursor will be positioned before the matched text, so you must advance it yourself if you determine it’s a valid match)
  • +
  • getDelimiterStack() - Returns the current delimiter stack. Only used in advanced use cases.
  • +
  • getFullMatch() - Returns the full string that matched you InlineParserMatch definition
  • +
  • getFullMatchLength() - Returns the length of the full match - useful for advancing the cursor
  • +
  • getSubMatches() - If your InlineParserMatch used a regular expression with capture groups, this will return the text matches by those groups.
  • +
  • getMatches() - Returns an array where index 0 is the “full match”, plus any sub-matches. It basically simulates preg_match()’s behavior.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the text at the current position for any reason. Other parsers will then have a chance to try parsing that text. If all registered parsers return false, the text will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed/matched text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::regex('@([A-Za-z0-9_]{1,15}(?!\w))');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+
+        // This seems to be a valid match
+        // Advance the cursor to the end of the match
+        $cursor->advanceBy($inlineContext->getFullMatchLength());
+
+        // Grab the Twitter handle
+        [$handle] = $inlineContext->getSubMatches();
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+// And here's how to hook it up:
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::oneOf(':)', ':(');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($inlineContext->getFullMatch() === ':)') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($inlineContext->getFullMatch() === ':(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance: +
      +
    • Avoid using overly-complex regular expressions in getMatchDefinition() - use the simplest regex you can and have parse() do the heavier validation
    • +
    • Have your parse() method return false as soon as possible.
    • +
    +
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/overview/index.html b/2.3/customization/overview/index.html new file mode 100644 index 0000000000..c6c0dad050 --- /dev/null +++ b/2.3/customization/overview/index.html @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders/configuration you need
  2. +
  3. Instantiate a MarkdownParser and HtmlRenderer using that Environment
  4. +
  5. Use the MarkdownParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  6. +
  7. Use the HtmlRenderer to convert the AST Document into HTML
  8. +
+ +

The MarkdownConverter class handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Renderer\HtmlRenderer;
+
+$environment = new Environment([
+    'html_input' => 'strip',
+]);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderDocument($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renderers convert the parsed blocks/inlines from the AST representation into HTML. When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/rendering/index.html b/2.3/customization/rendering/index.html new file mode 100644 index 0000000000..d85dca2787 --- /dev/null +++ b/2.3/customization/rendering/index.html @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Custom Rendering

+ +

Renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement NodeRendererInterface and its render() method. Note that in v2.0, both +block renderers and inline renderers share the same interface and method:

+ +

render()

+ +
public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The HtmlRenderer will call this method during the rendering process whenever a supported element is encountered.

+ +

If your renderer can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • Node $node - The encountered block or inline element that needs to be rendered
  • +
  • ChildNodeRendererInterface $childRenderer - If the given $node has children, use this to render those child elements
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the node and its contents, including any children. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\Util\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Renderers

+ +

When registering your renderer, you must tell the Environment which node element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// First param - the node class type that should use our renderer
+// Second param - instance of the renderer
+$environment->addRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple types:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
+use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements NodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(ThematicBreak::class, new TextDividerRenderer());
+
+ +

Note that thematic breaks should not contain children, which is why the $childRenderer is unused in this example. Otherwise we’d have to call code like this and return the result as part of the rendered HTML we’re generating here: $innerHtml = $childRenderer->renderNodes($node->children());

+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any child elements that your node might contain!
  • +
+ +

Wrapping Elements with HtmlDecorator

+ +

A utility class called HtmlDecorator is provided to make it easier to wrap the output of any renderer within an additional HTML tag with custom attributes and/or classes. To use it:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Renderer\HtmlDecorator;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\Extension\Table\TableRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(Table::class, new HtmlDecorator(new TableRenderer(), 'div', ['class' => 'table-responsive']));
+
+ +

XML Rendering

+ +

The XML renderer will automatically attempt to convert any AST nodes to XML by inspecting the name of the block/inline node and its attributes. You can instead control the XML element name and attributes by making your renderer implement XmlNodeRendererInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+use League\CommonMark\Xml\XmlNodeRendererInterface;
+
+class TextDividerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+
+    public function getXmlTagName(Node $node): string
+    {
+        return 'text_divider';
+    }
+
+    public function getXmlAttributes(Node $node): array
+    {
+        return ['character' => '='];
+    }
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/customization/slug-normalizer/index.html b/2.3/customization/slug-normalizer/index.html new file mode 100644 index 0000000000..6f6a50df8e --- /dev/null +++ b/2.3/customization/slug-normalizer/index.html @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + + + + Slug Normalizer - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Slug Normalizer

+ +

“Slugs” are strings used within href, name, and id HTML attributes to identify particular elements within a document.

+ +

Some extensions (like the HeadingPermalinkExtension) need the ability to convert user-provided text into these URL-safe slugs while also ensuring that these are unique throughout the generated HTML. The Environment provides a pre-built normalizer you can use for this purpose.

+ +

Usage

+ +

You can obtain a reference to the built-in slug normalizer by calling $environment->getSlugNormalizer();

+ +

To use this within your extension, have your parser/renderer/whatever implement EnvironmentAwareInterface and then implement the corresponding setEnvironment method like this:

+ +

+use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Environment\EnvironmentAwareInterface;
+
+class MyCustomParserOrRenderer implements EnvironmentAwareInterface
+{
+    private $slugNormalizer;
+
+    public function setEnvironment(EnvironmentInterface $environment): void
+    {
+        $this->slugNormalizer = $environment->getSlugNormalizer();
+    }
+}
+
+ +

You can then call $this->slugNormalizer->normalize($text) as needed.

+ +

Configuration

+ +

The slug_normalizer configuration section allows you to adjust the following options:

+ +

instance

+ +

You can change the string that is used as the “slug” by setting the instance option to any class that implements TextNormalizerInterface. +We provide a simple SlugNormalizer by default, but you may want to plug in a different library or create your own normalizer instead.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

max_length

+ +

This can be configured to limit the length of that slug to prevent overly-long values. By default, that limit is 255 characters. You may set this to any positive integer, or 0 for no limit.

+ +

(Note that generated slugs might be slightly longer than this “limit” if the unique option is enabled and the slug generator detects a duplicate slug and needs to add a suffix to make it unique.)

+ +

unique

+ +

This options controls whether slugs should be unique. Possible values include:

+ +
    +
  • 'document' (string; default) - Ensures slugs are unique within a single document
  • +
  • 'environment' (string) - Ensures slugs are unique across multiple documents - see below
  • +
  • false (boolean) - Disables unique slug generation
  • +
+ +

You might have a use case where you’re converting several different Markdown documents on the same page and so you’d like to ensure that none of those documents use conflicting slugs. In that case, you should set the scope option to 'environment' to ensure that a single instance of a MarkdownConverter (which uses a single Environment) will never produce the same slug twice during its lifetime (which usually lasts the entire duration of a single HTTP request).

+ +

If you need complete control over how unique slugs are generated, make your 'instance' implement UniqueSlugNormalizerInterface; otherwise, we’ll simply append incremental numbers to slugs to ensure they are unique.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/attributes/index.html b/2.3/extensions/attributes/index.html new file mode 100644 index 0000000000..c6cac0397d --- /dev/null +++ b/2.3/extensions/attributes/index.html @@ -0,0 +1,486 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+ +

This results in the following output:

+ +
<blockquote title="Blockquote title">
+<p>A nice blockquote</p>
+</blockquote>
+
+ +

CSS-selector-style declarations can be used to set the id and class attributes:

+ +
{#id .class}
+## Header
+
+ +

Output:

+ +
<h2 class="class" id="id">Header</h2>
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Output:

+ +
<p>This is <em style="color: red">red</em>.</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/autolinks/index.html b/2.3/extensions/autolinks/index.html new file mode 100644 index 0000000000..bce037ef6e --- /dev/null +++ b/2.3/extensions/autolinks/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a Mention Parser outside of this extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/commonmark/index.html b/2.3/extensions/commonmark/index.html new file mode 100644 index 0000000000..3a465e30e8 --- /dev/null +++ b/2.3/extensions/commonmark/index.html @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically installed for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new Environment with the core extension
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/default-attributes/index.html b/2.3/extensions/default-attributes/index.html new file mode 100644 index 0000000000..d1edfd1484 --- /dev/null +++ b/2.3/extensions/default-attributes/index.html @@ -0,0 +1,523 @@ + + + + + + + + + + + + + + + + + Default Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Default Attributes

+ +

The DefaultAttributesExtension allows you to apply default HTML classes and other attributes using configuration options.

+ +

It works by applying the attributes to the nodes during the DocumentParsedEvent event - right after the nodes are parsed but before they are rendered. +(As a result, it’s possible that renderers may add other attributes - the goal of this extension is only to provide custom defaults.)

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DefaultAttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Node\Block\Paragraph;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'default_attributes' => [
+        Heading::class => [
+            'class' => static function (Heading $node) {
+                if ($node->getLevel() === 1) {
+                    return 'title-main';
+                } else {
+                    return null;
+                }
+            },
+        ],
+        Table::class => [
+            'class' => 'table',
+        ],
+        Paragraph::class => [
+            'class' => ['text-center', 'font-comic-sans'],
+        ],
+        Link::class => [
+            'class' => 'btn btn-link',
+            'target' => '_blank',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new DefaultAttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a default_attributes array. Each key in the array should be a FQCN for the node class you wish to apply the default attribute to, and the values should be a map of attribute names to attribute values.

+ +

Attribute values may be any of the following types:

+ +
    +
  • string
  • +
  • string[]
  • +
  • bool
  • +
  • callable (parameter is the Node, return value may be string|string[]|bool)
  • +
+ +

Examples

+ +

Here’s an example that will apply Bootstrap 4 classes and attributes:

+ +
$config = [
+    'default_attributes' => [
+        Table::class => [
+            'class' => ['table', 'table-responsive'],
+        ],
+        BlockQuote::class => [
+            'class' => 'blockquote',
+        ],
+    ],
+];
+
+ +

Here’s a more complex example that uses a callable to add a class only if the paragraph immediately follows an <h1> heading:

+ +
$config = [
+    'default_attributes' => [
+        Paragraph::class => [
+            'class' => static function (Paragraph $paragraph) {
+                if ($paragraph->previous() instanceof Heading && $paragraph->previous()->getLevel() === 1) {
+                    return 'lead';
+                }
+
+                return null;
+            },
+        ],
+    ],
+];
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/description-lists/index.html b/2.3/extensions/description-lists/index.html new file mode 100644 index 0000000000..2ac3be7f32 --- /dev/null +++ b/2.3/extensions/description-lists/index.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + Description List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Description List Extension

+ +

The DescriptionListExtension adds Markdown Extra-style description lists to facilitate the creation of <dl>, <dt>, and <dd> HTML using Markdown.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DescriptionListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\DescriptionList\DescriptionListExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DescriptionListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some markdown goes here');
+
+ +

Syntax

+ +

The syntax is based directly on the rules and logic implemented by the Markdown Extra library. Here are some examples of sample Markdown input and HTML output demonstrating the syntax:

+ +
Apple
+:   Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.
+:   An American computer company.
+
+Orange
+:   The fruit of an evergreen tree of the genus Citrus.
+
+ +
<dl>
+    <dt>Apple</dt>
+    <dd>Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.</dd>
+    <dd>An American computer company.</dd>
+
+    <dt>Orange</dt>
+    <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
+</dl>
+
+ +

See the Markdown Extra documentation or our own spec for additional examples.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/disallowed-raw-html/index.html b/2.3/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..37755551e6 --- /dev/null +++ b/2.3/extensions/disallowed-raw-html/index.html @@ -0,0 +1,480 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically escapes certain HTML tags when rendering raw HTML, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Customize the extension's configuration if needed
+// Default values are shown below - you can omit this configuration if you're happy with those defaults
+// and don't want to customize them
+$config = [
+    'disallowed_raw_html' => [
+        'disallowed_tags' => ['title', 'textarea', 'style', 'xmp', 'iframe', 'noembed', 'noframes', 'script', 'plaintext'],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I cannot change the page <title>anymore</title>');
+
+ +

Configuration

+ +

This extension can be configured by providing a disallowed_raw_html array with the following nested configuration options. The defaults are shown in the code example above.

+ +

disallowed_tags

+ +

An array containing a list of tags that should be escaped.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/embed/index.html b/2.3/extensions/embed/index.html new file mode 100644 index 0000000000..da42af5aaa --- /dev/null +++ b/2.3/extensions/embed/index.html @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + Embed Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Embed Extension

+ +

This extension can embed rich content (like videos, tweets, etc.) from other websites.

+ +

The syntax is very simple - simply place any https:// URL on its own line like this:

+ +
Check out this video!
+
+https://www.youtube.com/watch?v=dQw4w9WgXcQ
+
+ +

If the link points to embeddable content, it will be replaced with the rich HTML needed to embed it:

+ +
<p>Check out this video:</p>
+<iframe width="200" height="113" src="https://www.youtube.com/embed/dQw4w9WgXcQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

You’ll also need to install a third-party OEmbed library - see the Adapter section below.

+ +

Usage

+ +

Configure your Environment as usual and add the EmbedExtension provided by this package:

+ +
use Embed\Embed;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Embed\EmbedExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'embed' => [
+        'adapter' => new OscaroteroEmbedAdapter(), // See the "Adapter" documentation below
+        'allowed_domains' => ['youtube.com', 'twitter.com', 'github.com'],
+        'fallback' => 'link',
+    ],
+];
+
+// Configure the Environment with all whatever other extensions you want
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new EmbedExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
+
+ +

Configuration

+ +

This extension supports the following configuration options under the embed configuration:

+ +

adapter option

+ +

Any instance of EmbedAdapterInterface - see the “Adapter” section below.

+ +

allowed_domains option

+ +

This option defines a list of hosts that you wish to allow embedding content from. For example, setting this to +['youtube.com'] would only allow videos from YouTube to be embedded. +It’s extremely important that you only include websites you trust since they’ll be providing HTML that is directly embedded in your website.

+ +

Any subdomains of these domains will also be allowed. For example, ['youtube.com'] would allow embedding from youtube.com or www.youtube.com.

+ +

As an additional safety measure, we recommend that you also use a Content Security Policy (CSP) +to prevent unexpected content from being embedded.

+ +

By default, this option is an empty array ([]), which means that all domains are allowed.

+ +

fallback option

+ +

This options defines the behavior when a URL cannot be embedded, either because it’s not in the list of allowed_domains, +or because the adapter could not find embeddable content for that URL.

+ +

There are two possible values for this option:

+ +
    +
  • 'link' - the URL will be kept in the document as a link (default) +-'remove' - the URL will be completely removed from the document
  • +
+ +

Adapter

+ +

league/commonmark doesn’t know how to obtain the embeddable HTML for a given URL - this must be done by an external library.

+ +

embed/embed Adapter

+ +

We do provide an adapter for the popular embed/embed library. if you’d like to use that. We like this library +because it supports fetching multiple URLs in parallel, which is ideal for performance, and it supports a wide range +of embeddable content.

+ +

To use that library, you’ll need to composer install embed/embed and then pass new OscaroteroEmbedAdapter() as the adapter +configuration option, as shown in the Usage section above.

+ +

Need to customize the maximum width/height of the embedded content? You can do that by instantiating the service provided by +embed/embed, configuring it as needed, and passing that customized instance into the adapter:

+ +
use Embed\Embed;
+use League\CommonMark\Extension\Embed\Bridge\OscaroteroEmbedAdapter;
+
+// Configure the Embed library itself
+$embedLibrary = new Embed();
+$embedLibrary->setSettings([
+    'oembed:query_parameters' => [
+        'maxwidth' => 800,
+        'maxheight' => 600,
+    ],
+    'twitch:parent' => 'example.com',
+    'facebook:token' => '1234|5678',
+    'instagram:token' => '1234|5678',
+    'twitter:token' => 'asdf',
+]);
+
+// Inject it into our adapter
+$config = [
+    'adapter' => new OscaroteroEmbedAdapter($embedLibrary),
+];
+
+// Instantiate your CommonMark environment and converter like usual
+// ...
+
+ +

Custom Adapter

+ +

If you prefer to use a different library, you’ll need to implement our EmbedAdapterInterface yourself with +whatever OEmbed library you choose.

+ +

Tips

+ +

If you need to wrap the HTML in a container tag, consider using the HtmlDecorator renderer:

+ +
$environment->addRenderer(Embed::class, new HtmlDecorator(new EmbedRenderer(), 'div', ['class' => 'embeded-content']));
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/external-links/index.html b/2.3/extensions/external-links/index.html new file mode 100644 index 0000000000..fe49a3299f --- /dev/null +++ b/2.3/extensions/external-links/index.html @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class MyCustomLinkRenderer implements NodeRendererInterface
+{
+    /**
+     * @param Node                       $node
+     * @param ChildNodeRendererInterface $childRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        if (!($node instanceof Link)) {
+            throw new \InvalidArgumentException('Incompatible node type: ' . \get_class($node));
+        }
+
+        if ($node->data->get('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to automagically add an external link icon by targeting the html_class given in the configuration:

+ +
// Font Awesome example:
+a[target="_blank"]::after,
+a.external::after {
+   content: "\f08e";
+   font: normal normal normal 14px/1 FontAwesome;
+}
+
+// Glyphicon example:
+a[target="_blank"]::after,
+a.external::after {
+  @extend .glyphicon;
+  content: "\e164";
+  margin-left: .5em;
+  margin-right: .25em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/footnotes/index.html b/2.3/extensions/footnotes/index.html new file mode 100644 index 0000000000..46a4e818cb --- /dev/null +++ b/2.3/extensions/footnotes/index.html @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1"></a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'backref_symbol'     => '↩',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

backref_symbol

+ +

This string option sets the symbol used as the contents of the footnote backreference link. It defaults to \League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer::DEFAULT_SYMBOL = '↩'.

+ +

If you want to use a custom icon, set this to an empty string '' and take a look at the Adding Icons section below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ +

Adding Icons

+ +

You can use CSS to add a custom icon instead of providing a backref_symbol:

+ +
$config = [
+    'footnote' => [
+        'backref_class' => 'footnote-backref',
+        'backref_symbol' => '',
+    ],
+];
+
+ +

Then target the backref_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.footnote-backref::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/front-matter/index.html b/2.3/extensions/front-matter/index.html new file mode 100644 index 0000000000..76706516fd --- /dev/null +++ b/2.3/extensions/front-matter/index.html @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + Front Matter Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Front Matter Extension

+ +

The FrontMatterExtension adds the ability to parse YAML front matter from the Markdown document and include that in the return result.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

You will also need to install symfony/yaml or the YAML extension for PHP to use this extension. For symfony/yaml:

+ +
composer require symfony/yaml
+
+ +

(You can use any version of symfony/yaml 2.3 or higher, though we recommend using 4.0 or higher.)

+ +

Front Matter Syntax

+ +

This extension follows the Jekyll Front Matter syntax. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Here is a basic example:

+ +
---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+
+ +

This will produce a front matter array similar to this:

+ +
$parsedFrontMatter = [
+    'layout' => 'post',
+    'title' => 'I Love Markdown',
+    'tags' => [
+        'test',
+        'example',
+    ],
+];
+
+ +

And the HTML output will only contain the one heading:

+ +
<h1>Hello World!</h1>
+
+ +

Usage

+ +

Configure your Environment as usual and add the FrontMatterExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FrontMatterExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+// A sample Markdown file with some front matter:
+$markdown = <<<MD
+---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+MD;
+
+$result = $converter->convert($markdown);
+
+// Grab the front matter:
+if ($result instanceof RenderedContentWithFrontMatter) {
+    $frontMatter = $result->getFrontMatter();
+}
+
+// Output the HTML using any of these:
+echo $result;               // implicit string cast
+// or:
+echo (string) $result;      // explicit string cast
+// or:
+echo $result->getContent();
+
+ +

Parsing Front Matter Only

+ +

You don’t have to parse the entire file (including all the Markdown) if you only want the front matter. You can either instantiate the front matter parser yourself and call it directly, like this:

+ +
use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+// For `symfony/yaml`
+$frontMatterParser = new FrontMatterParser(new SymfonyYamlFrontMatterParser());
+// For YAML extension
+$frontMatterParser = new FrontMatterParser(new LibYamlFrontMatterParser());
+$result = $frontMatterParser->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

Or you can use the getFrontMatterParser() method from the extension:

+ +
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+$frontMatterExtension = new FrontMatterExtension();
+$result = $frontMatterExtension->getFrontMatterParser()->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

This latter approach may be more convenient if you have already instantiated a FrontMatterExtension object you’re adding to the Environment somewhere and just want to call that.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/github-flavored-markdown/index.html b/2.3/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..765ac22e5a --- /dev/null +++ b/2.3/extensions/github-flavored-markdown/index.html @@ -0,0 +1,471 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark and GFM parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/heading-permalinks/index.html b/2.3/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..a98020e76b --- /dev/null +++ b/2.3/extensions/heading-permalinks/index.html @@ -0,0 +1,619 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\MarkdownConverter;
+
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'content',
+        'fragment_prefix' => 'content',
+        'insert' => 'before',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+        'aria_hidden' => true,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

fragment_prefix

+ +

This should be a string you want prepended to the URL fragment in the link’s href attribute. This should typically be set to the same value as id_prefix for links to work properly. However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to “rewrite” them.

+ +

For example, to emulate how GitHub heading permalinks work, set id_prefix to 'user-content', set fragment_prefix to '', and insert some JavaScript into the page like this:

+ +
var scrollToPermalink = function() {
+    var link = document.getElementById('user-content-' + window.location.hash);
+    if (link) {
+        link.scrollIntoView({behavior: 'smooth'});
+    }
+};
+
+window.addEventListener('hashchange', scrollToPermalink);
+if (window.location.hash) {
+    scrollToPermalink();
+}
+
+ +

insert

+ +

This controls whether the anchor is added to the beginning of the <h1>, <h2> etc. tag or to the end. Can be set to either 'before' or 'after'.

+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should have permalinks added. By default, all 6 levels (1, 2, 3, 4, 5, and 6) will have them. You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

aria_hidden

+ +

This option sets the aria-hidden attribute on the <a> tag. This defaults to aria-hidden="true".

+ +

Setting this option to false would render the <a> tag excluding the aria-hidden entirely.

+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/inlines-only/index.html b/2.3/extensions/inlines-only/index.html new file mode 100644 index 0000000000..7933812cb6 --- /dev/null +++ b/2.3/extensions/inlines-only/index.html @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new, empty environment
+$environment = new Environment($config);
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/mentions/index.html b/2.3/extensions/mentions/index.html new file mode 100644 index 0000000000..0f38f9f58f --- /dev/null +++ b/2.3/extensions/mentions/index.html @@ -0,0 +1,670 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which prefix you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting prefix, a regular expression to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same prefix, the first mention parser to
+        // successfully match and return a properly constructed Mention object (where the URL has been set) will be the
+        // the mention parser that is used. In this example, the GitHub handle would actually match first because
+        // there isn't any real validation to check whether https://www.github.com/colinodell exists. However, in
+        // CMS applications, you could check whether its a local user first, then check Twitter and then GitHub, etc.
+        'twitter_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[A-Za-z0-9_]{1,15}(?!\w)',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on GitHub: @colinodell');
+// Output:
+// <p>Follow me on GitHub: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Prefix └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the prefix (in this case, @). You can use any prefix, regular expression pattern (without opening/closing delimiter or modifiers), or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Node\Inline\AbstractInline;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Inline\Element\AbstractInline;
+
+class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'user_url_generator' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z0-9]+',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/overview/index.html b/2.3/extensions/overview/index.html new file mode 100644 index 0000000000..623e7ec505 --- /dev/null +++ b/2.3/extensions/overview/index.html @@ -0,0 +1,602 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Default AttributesEasily apply default HTML classes using configuration options to match your site’s styles2.0.0 
Description ListsCreate <dl> description lists using Markdown Extra’s syntax2.0.0 
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
EmbedEmbed rich content (like videos, tweets, and more) from other websites2.3.0 
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
Front MatterParses YAML front matter from your Markdown input2.0.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the extensions you need
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the other extensions you need
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/smart-punctuation/index.html b/2.3/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..6a10c05aa4 --- /dev/null +++ b/2.3/extensions/smart-punctuation/index.html @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/strikethrough/index.html b/2.3/extensions/strikethrough/index.html new file mode 100644 index 0000000000..9b64b2cede --- /dev/null +++ b/2.3/extensions/strikethrough/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/table-of-contents/index.html b/2.3/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..be25e71558 --- /dev/null +++ b/2.3/extensions/table-of-contents/index.html @@ -0,0 +1,601 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/tables/index.html b/2.3/extensions/tables/index.html new file mode 100644 index 0000000000..c3014f3ee9 --- /dev/null +++ b/2.3/extensions/tables/index.html @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => false,
+            'tag' => 'div',
+            'attributes' => [],
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Configuration

+ +

Wrapping Container

+ +

You can “wrap” the table with a container element by configuring the following options:

+ +
    +
  • enabled: (boolean) Whether to wrap the table with a container element. Defaults to false.
  • +
  • tag: (string) The tag name of the container element. Defaults to div.
  • +
  • attributes: (array) An array of attributes to apply to the container element. Defaults to [].
  • +
+ +

For example, to wrap all tables within a <div class="table-responsive"> container element:

+ +
$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => true,
+            'tag' => 'div',
+            'attributes' => ['class' => 'table-responsive'],
+        ],
+    ],
+];
+
+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/extensions/task-lists/index.html b/2.3/extensions/task-lists/index.html new file mode 100644 index 0000000000..6978a66764 --- /dev/null +++ b/2.3/extensions/task-lists/index.html @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convert($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/index.html b/2.3/index.html new file mode 100644 index 0000000000..b5dc052357 --- /dev/null +++ b/2.3/index.html @@ -0,0 +1,447 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello, World!')->getContent();
+
+// <h1>Hello, World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/installation/index.html b/2.3/installation/index.html new file mode 100644 index 0000000000..ef8258cf20 --- /dev/null +++ b/2.3/installation/index.html @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Installation

+ +

The recommended installation method is via Composer.

+ +
composer require "league/commonmark:^2.3"
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^2.3. This is equivalent to >=2.3 <3.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/security/index.html b/2.3/security/index.html new file mode 100644 index 0000000000..feb1449730 --- /dev/null +++ b/2.3/security/index.html @@ -0,0 +1,497 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convert($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like html-sanitizer). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/support/index.html b/2.3/support/index.html new file mode 100644 index 0000000000..5012a9e5dc --- /dev/null +++ b/2.3/support/index.html @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + + + + Support - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Support

+ +

Here are some useful resources to help you use this project:

+ + + +

Supported Versions

+ +

See our security policy for information about the support cycle for bug fixes and security updates.

+ +

Reporting a Vulnerability

+ +

If you discover a security vulnerability within this package, please use the Tidelift security contact form or email Colin O’Dell at colinodell@gmail.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/upgrading/index.html b/2.3/upgrading/index.html new file mode 100644 index 0000000000..b48f268be4 --- /dev/null +++ b/2.3/upgrading/index.html @@ -0,0 +1,415 @@ + + + + + + + + + + + + + + + + + Upgrading from 2.2 to 2.3 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

Upgrading from 2.2 to 2.3

+ +

Avoid deprecated interface

+ +

MarkdownRendererInterface has been deprecated and will be removed in the next major version. Please use DocumentRendererInterface instead.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.3/xml/index.html b/2.3/xml/index.html new file mode 100644 index 0000000000..565f0d76b0 --- /dev/null +++ b/2.3/xml/index.html @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + XML Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + +

This is the documentation for the unsupported version 2.3. Please consider upgrading your code to the latest stable version

+ + +

XML Rendering

+ +

Version 2.0 introduced the ability to render Markdown Document objects in XML. This is particularly useful for debugging custom extensions as you can see the XML representation of the Abstract Syntax Tree.

+ +

To convert Markdown to XML, you would instantiate a MarkdownToXmlConverter with an Environment and then call convert() on any Markdown.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Xml\MarkdownToXmlConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$converter = new MarkdownToXmlConverter($environment);
+
+echo $converter->convert('# **Hello** World!');
+
+ +

This will display XML output like this:

+ +
<?xml version="1.0" encoding="UTF-8"?>
+<document xmlns="http://commonmark.org/xml/1.0">
+    <heading level="1">
+        <strong>
+            <text>Hello</text>
+        </strong>
+        <text> World!</text>
+    </heading>
+</document>
+
+ +

Alternatively, if you already have a Document object you want to visualize in XML, you can use theXmlRenderer class to convert it to XML.

+ +

Return Value

+ +

Like with CommonMarkConverter::convert(), the renderDocument() actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final XML output.

+ +

Customizing the XML Output

+ +

See the rendering documentation for information on customizing the XML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/basic-usage/index.html b/2.4/basic-usage/index.html new file mode 100644 index 0000000000..f86a36d91c --- /dev/null +++ b/2.4/basic-usage/index.html @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Basic Usage

+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Using Extensions

+ +

The CommonMarkConverter and GithubFlavoredMarkdownConverter shown above automatically configure the environment for you, but if you want to use additional extensions you’ll need to avoid those classes and use the generic MarkdownConverter class instead to customize the environment with whatever extensions you wish to use:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+$environment->addExtension(new InlinesOnlyExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+// <p><strong>Hello World!</strong></p>
+
+ +

Configuration

+ +

If you’re using the CommonMarkConverter or GithubFlavoredMarkdownConverter class you can pass configuration options directly into their constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

See the configuration section for more information on the available configuration options.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ +

Return Value

+ +

The convert() method actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final HTML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/changelog/index.html b/2.4/changelog/index.html new file mode 100644 index 0000000000..584ba72a86 --- /dev/null +++ b/2.4/changelog/index.html @@ -0,0 +1,627 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Changelog

+ +

All notable changes made in 2.x releases are shown below. See the full list of releases for the complete changelog.

+ +

2.5.3 - 2024-08-16

+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.1, including: +
      +
    • Remove source, add search to list of recognized block tags
    • +
    +
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.2…2.5.3

+ +

2.5.2 - 2024-08-14

+ +

Changed

+ +
    +
  • Boolean attributes now require an explicit true value (#1040)
  • +
+ +

Fixed

+ +
    +
  • Fixed regression where text could be misinterpreted as an attribute (#1040)
  • +
+ +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.1…2.5.2

+ +

2.5.1 - 2024-07-24

+ +

Fixed

+ +
    +
  • Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
  • +
  • Fixed incorrect Table start line numbers (#1037)
  • +
+ +

New Contributors

+
    +
  • @jasonvarga made their first contribution in https://github.com/thephpleague/commonmark/pull/1035
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.0…2.5.1

+ +

2.5.0 - 2024-07-22

+ +

Added

+ +
    +
  • The AttributesExtension now supports attributes without values (#985, #986)
  • +
  • The AutolinkExtension exposes two new configuration options to override the default behavior (#969, #987): +
      +
    • autolink/allowed_protocols - an array of protocols to allow autolinking for
    • +
    • autolink/default_protocol - the default protocol to use when none is specified
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.0, including: +
      +
    • Allow closing fence to be followed by tabs
    • +
    • Remove restrictive limitation on inline comments
    • +
    • Unicode symbols now treated like punctuation (for purposes of flankingness)
    • +
    • Trailing tabs on the last line of indented code blocks will be excluded
    • +
    • Improved HTML comment matching
    • +
    +
  • +
  • Paragraphs only containing link reference definitions will be kept in the AST until the Document is finalized +
      +
    • (These were previously removed immediately after parsing the Paragraph)
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed list tightness not being determined properly in some edge cases
  • +
  • Fixed incorrect ending line numbers for several block types in various scenarios
  • +
  • Fixed lowercase inline HTML declarations not being accepted
  • +
+ +

New Contributors

+
    +
  • @svenluijten made their first contribution in https://github.com/thephpleague/commonmark/pull/986
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.4…2.5.0

+ +

2.4.4 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.3…2.4.4

+ +

2.4.3 - 2024-07-22

+ +

Fixed

+ +
    +
  • Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
  • +
  • Fixed UrlAutolinkParser incorrectly parsing text containing www anywhere before an autolink (#1025)
  • +
+ +

New Contributors

+
    +
  • @nfreader made their first contribution in https://github.com/thephpleague/commonmark/pull/1016
  • +
  • @macbookandrew made their first contribution in https://github.com/thephpleague/commonmark/pull/1025
  • +
  • @xavierlacot made their first contribution in https://github.com/thephpleague/commonmark/pull/1013
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.2…2.4.3

+ +

2.4.2 - 2024-02-02

+ +

Fixed

+ +
    +
  • Fixed declaration parser being too strict
  • +
  • FencedCodeRenderer: don’t add language- to class if already prefixed
  • +
+ +

New Contributors

+
    +
  • @sergiy-petrov made their first contribution in https://github.com/thephpleague/commonmark/pull/997
  • +
  • @clustermin made their first contribution in https://github.com/thephpleague/commonmark/pull/988
  • +
  • @peter279k made their first contribution in https://github.com/thephpleague/commonmark/pull/996
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.1…2.4.2

+ +

2.4.1 - 2023-08-30

+ +

Fixed

+ +
    +
  • Fixed ExternalLinkProcessor not fully disabling the rel attribute when configured to do so (#992)
  • +
+ +

2.4.0 - 2023-03-24

+ +

See the upgrading guide for more information about the exception-related changes

+ +

Added

+ +
    +
  • Added generic CommonMarkException marker interface for all exceptions thrown by the library
  • +
  • Added several new specific exception types implementing that marker interface: +
      +
    • AlreadyInitializedException
    • +
    • InvalidArgumentException
    • +
    • IOException
    • +
    • LogicException
    • +
    • MissingDependencyException
    • +
    • NoMatchingRendererException
    • +
    • ParserLogicException
    • +
    +
  • +
  • Added more configuration options to the Heading Permalinks extension (#939): +
      +
    • heading_permalink/apply_id_to_heading - When true, the id attribute will be applied to the heading element itself instead of the <a> tag
    • +
    • heading_permalink/heading_class - class to apply to the heading element
    • +
    • heading_permalink/insert - now accepts none to prevent the creation of the <a> link
    • +
    +
  • +
  • Added new table/alignment_attributes configuration option to control how table cell alignment is rendered (#959)
  • +
+ +

Changed

+ +
    +
  • Change several thrown exceptions from RuntimeException to LogicException (or something extending it), including: +
      +
    • CallbackGenerators that fail to set a URL or return an expected value
    • +
    • MarkdownParser when deactivating the last block parser or attempting to get an active block parser when they’ve all been closed
    • +
    • Adding items to an already-initialized Environment
    • +
    • Rendering a Node when no renderer has been registered for it
    • +
    +
  • +
  • HeadingPermalinkProcessor now throws InvalidConfigurationException instead of RuntimeException when invalid config values are given.
  • +
  • HtmlElement::setAttribute() no longer requires the second parameter for boolean attributes
  • +
  • Several small micro-optimizations
  • +
  • Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
  • +
+ +

Fixed

+ +
    +
  • Fixed inaccurate @throws docblocks throughout the codebase, including ConverterInterface, MarkdownConverter, and MarkdownConverterInterface. +
      +
    • These previously suggested that only \RuntimeExceptions were thrown, which was inaccurate as \LogicExceptions were also possible.
    • +
    +
  • +
+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/configuration/index.html b/2.4/configuration/index.html new file mode 100644 index 0000000000..78f8693820 --- /dev/null +++ b/2.4/configuration/index.html @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Configuration

+ +

Many aspects of this library’s behavior can be tweaked using configuration options.

+ +

You can provide an array of configuration options to the Environment or converter classes when creating them:

+ +
$config = [
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'commonmark' => [
+        'enable_em' => true,
+        'enable_strong' => true,
+        'use_asterisk' => true,
+        'use_underscore' => true,
+        'unordered_list_markers' => ['-', '*', '+'],
+    ],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => PHP_INT_MAX,
+    'slug_normalizer' => [
+        'max_length' => 255,
+    ],
+];
+
+ +

If you’re using the basic CommonMarkConverter or GithubFlavoredMarkdown classes, simply pass the configuration array into the constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

Here’s a list of the core configuration options available:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: PHP_INT_MAX). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested.
  • +
  • slug_normalizer - Array of options for configuring how URL-safe slugs are created; see the slug normalizer docs for more details +
      +
    • instance - An alternative normalizer to use (defaults to the included SlugNormalizer)
    • +
    • max_length - Limits the size of generated slugs (defaults to 255 characters)
    • +
    • unique - Controls whether slugs should be unique per 'document' (default) or per 'environment'; can be disabled with false
    • +
    +
  • +
+ +

Additional configuration options are available for most of the available extensions - refer to their individual documentation for more details. For example, the CommonMark core extension offers these additional options:

+ +
    +
  • commonmark - Array of options for configuring the CommonMark core extension: +
      +
    • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
    • +
    • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
    • +
    • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
    • +
    • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
    • +
    • unordered_list_markers - Array of characters that can be used to indicate a bulleted list (default: ["-", "*", "+"])
    • +
    +
  • +
+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/abstract-syntax-tree/index.html b/2.4/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..b459d66ab5 --- /dev/null +++ b/2.4/customization/abstract-syntax-tree/index.html @@ -0,0 +1,710 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the MarkdownParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent see the (Event Dispatcher documentation)
  • +
+ +

Visualization

+ +

Even with an interactive debugger it can be tricky to view an entire tree at once. Consider using the XmlRenderer to provide a simple text-based representation of the AST for debugging purposes.

+ +

Node Traversal

+ +

There are four different ways to traverse/iterate the Nodes within the AST:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodProsCons
Manual TraversalBest way to access/check direct relatives of nodesNot useful for iteration
Iterating the TreeFast and efficientPossible unexpected behavior when adding/removing sibling nodes while iterating
Walking the TreeFull control over iterationUp to twice as slow as iteration; adding/removing nodes while iterating can lead to weird behaviors
Querying NodesEasier to write and understand; no weird behaviorsNot memory efficient
+ +

Each is described in more detail below

+ +

Manual Traversal

+ +

The following methods can be used to manually traverse from one Node to any of its direct relatives:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

This is best suited for situations when you need to know information about those relatives.

+ +

Iterating the Tree

+ +

If you’d like to iterate through all the nodes, use the iterator() method to obtain an iterator that will loop through each node in the tree (using pre-order traversal):

+ +
foreach ($document->iterator() as $node) {
+    echo 'Current node: ' . get_class($node) . "\n";
+}
+
+ +

Given an AST like this (XML representation):

+ +
<document>
+  <heading level="1">
+    <text>Hello World!</text>
+  </heading>
+  <paragraph>
+    <text>This is an example of </text>
+    <strong>
+      <text>CommonMark</text>
+    </strong>
+    <text>.</text>
+  </paragraph>
+</document>
+
+ +

The code above will output:

+ +
Current node: League\CommonMark\Node\Block\Document
+Current node: League\CommonMark\Extension\CommonMark\Node\Block\Heading
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Block\Paragraph
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Extension\CommonMark\Node\Inline\Strong
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Inline\Text
+
+ +

This iterator doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes. It’s also very CPU and memory-efficient.

+ +

Be careful when modifying nodes while iterating the tree as some of those changes may affect the current iteration process, especially for sibling nodes that come after the current one. For example, if you remove the current node’s next() sibling, the next loop of that iteration will still include the removed sibling even though it was successfully removed from the AST. Similarly, any new siblings that are added won’t be visited on the next loop.

+ +

Walking the Tree

+ +

If you’d like to walk through all the nodes, visiting each one as you enter and leave it, use the walker() method to obtain an instance of NodeWalker. This also uses pre-order traversal but emitting NodeWalkerEvents along the way:

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'Now ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

Using the same example AST in the previous section, this code will output:

+ +
Now entering a League\CommonMark\Node\Block\Document node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Block\Paragraph node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Node\Block\Paragraph node
+Now leaving a League\CommonMark\Node\Block\Document node
+
+ +

This approach offers many of the same benefits as the simple iteration shown in the previous section such as memory efficiency and no recursion. The key differences come from how you enter and leave nodes:

+ +
    +
  1. Iteration can potentially take twice as long - not ideal for performance
  2. +
  3. Provides you with more control over exactly when an action is taken on a node which is sometimes needed for certain AST manipulations
  4. +
  5. Also provides a resumeAt() method to override where it should iterate next
  6. +
+ +

But like with the iterator, be careful when adding/removing nodes while walking the tree, as there are even more subtle cases where the walker could even lose track of where it was, which may result in some nodes being visited multiple times or not at all.

+ +

Querying Nodes

+ +

If you’re trying to locate certain nodes to perform actions on them, querying the nodes from the AST might be easier to implement. This can be done with the Query class:

+ +
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Node\Block\Paragraph;
+use League\CommonMark\Node\Query;
+
+// Find all paragraphs and blockquotes that contain links
+$matchingNodes = (new Query())
+    ->where(Query::type(Paragraph::class))
+    ->orWhere(Query::type(BlockQuote::class))
+    ->andWhere(Query::hasChild(Query::type(Link::class)))
+    ->findAll($document);
+
+foreach ($matchingNodes as $node) {
+    // TODO: Do something with them
+}
+
+ +

Each condition passed into where(), orWhere(), or andWhere() must be a callable “filter” that accepts a Node and returns true or false. We provide several methods that can help create these filters for you:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Query::type(string $class)Creates a filter that matches nodes with the given class name
Query::hasChild()Creates a filter that matches nodes which contain at least one child
Query::hasChild(callable $condition)Creates a filter that matches nodes which contain at least one child that matches the inner $condition
Query::hasParent()Creates a filter that matches nodes which have a parent
Query::hasParent(callable $condition)Creates a filter that matches nodes which have a parent that matches the inner $condition
+ +

You can of course create your own custom filters/conditions using an anonymous function or by implementing ExpressionInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Node\Query;
+use League\CommonMark\Node\Query\ExpressionInterface;
+
+class ChildCountGreaterThan implements ExpressionInterface
+{
+    private $count;
+
+    public function __construct(int $count)
+    {
+        $this->count = $count;
+    }
+
+    public function __invoke(Node $node) : bool{
+        return count($node->children()) > $this->count;
+    }
+}
+
+$query = (new Query())
+    ->where(function (Node $node): bool { return $node->data->has('attributes/class'); })
+    ->andWhere(new ChildCountGreaterThan(3));
+
+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ +

Data Storage

+ +

Each Node has a property called data which is a Data (array-like) object. This can be used to store any arbitrary data you’d like on the node:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text1 = new Text('Hello, world!');
+$text1->data->set('language', 'English');
+$text1->data->set('is_good_translation', true);
+
+$text2 = new Text('Bonjour monde!');
+$text2->data->set('language', 'French');
+$text2->data->set('is_good_translation', false);
+
+foreach ([$text1, $text2] as $text) {
+    if ($text->data->get('is_good_translation')) {
+        sprintf('In %s we would say: "%s"', $text->data->get('language'), $text->getLiteral());
+    } else {
+        sprintf('I think they would say "%s" in %s, but I\'m not sure.', $text->getLiteral(), $text->data->get('language'));
+    }
+}
+
+ +

You can also access deeply-nested paths using / or . as delimiters:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text = new Text('Hello, world!');
+$text->data->set('info', ['language' => 'English', 'is_good_translation' => true]);
+
+var_dump($text->data->get('info/language'));
+var_dump($text->data->get('info.is_good_translation'));
+
+$text->data->set('info/is_example', true);
+
+ +

HTML Attributes

+ +

The data property comes pre-instantiated with a single data element called attributes which is used to store any HTML attributes that need to be rendered. For example:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+$link = new Link('https://twitter.com/colinodell', '@colinodell');
+$link->data->append('attributes/class', 'social-link');
+$link->data->append('attributes/class', 'twitter');
+$link->data->set('attributes/target', '_blank');
+$link->data->set('attributes/rel', 'noopener');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/block-parsing/index.html b/2.4/customization/block-parsing/index.html new file mode 100644 index 0000000000..954358bf36 --- /dev/null +++ b/2.4/customization/block-parsing/index.html @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Block Parsing

+ +

At a high level, block parsing is a two-step process:

+ +
    +
  1. Using a BlockStartParserInterface to identify if/where a block start exists on the given line
  2. +
  3. Using a BlockContinueParserInterface to perform additional processing of the identified block
  4. +
+ +

So to implement a custom block parser you will actually need to implement both of these classes.

+ +

BlockStartParserInterface

+ +

Instances of this interface have a single tryStart() method:

+ +
/**
+ * Check whether we should handle the block at the current position
+ *
+ * @param Cursor                       $cursor
+ * @param MarkdownParserStateInterface $parserState
+ *
+ * @return BlockStart|null
+ */
+public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart;
+
+ +

Given a Cursor at the current position, plus some extra information about the state of the parser, this method is responsible for determining whether a particular type of block seems to exist at the given position. You don’t actually parse the block here - that’s the job of a BlockContinueParserInterface. Your only job here is to return whether or not a particular type of block does exist here, and if so which block parser should parse it.

+ +

If you find that you cannot parse the given block, you should return BlockStart::none(); from this function.

+ +

However, if the Markdown at the current position does indeed seem to be the type of block you’re looking for, you should return a BlockStart instance using the following static constructor pattern:

+ +
use League\CommonMark\Parser\Block\BlockStart;
+
+return BlockStart::of(new MyCustomParser())->at($cursor);
+
+ +

Unlike in 1.x, the Cursor state is no longer shared between parsers. You must therefore explicitly provide the BlockStart object with a copy of your cursor at the correct, post-parsing position.

+ +

NOTE: If your custom block starts with a letter character you’ll need to add your parser to the environment with a priority of 250 or higher. This is due to a performance optimization where such lines are usually skipped.

+ +

BlockContinueParserInterface

+ +

The previous interface only helps the engine identify where a block starts. Additional information about the block, as well as the ability to parse additional lines of input, is all handled by the BlockContinueParserInterface.

+ +

This interface has several methods, so it’s usually easier to extend from AbstractBlockContinueParser instead, which sets most of the methods to use typical defaults you can override as needed.

+ +

getBlock()

+ +
public function getBlock(): AbstractBlock;
+
+ +

Each instance of a BlockContinueParserInterface is associated with a new block that is being parsed. This method here returns that block.

+ +

isContainer()

+ +
public function isContainer(): bool;
+
+ +

This method returns whether or not the block is a “container” capable of containing other blocks as children.

+ +

canContain()

+ +
public function canContain(AbstractBlock $childBlock): bool;
+
+ +

This method returns whether the current block being parsed can contain the given child block.

+ +

canHaveLazyContinuationLines()

+ +
public function canHaveLazyContinuationLines(): bool;
+
+ +

This method returns whether or not this parser should also receive subsequent lines of Markdown input. This is primarily used when a block can span multiple lines, like code blocks do.

+ +

addLine()

+ +
public function addLine(string $line): void;
+
+ +

If canHaveLazyContinuationLines() returned true, this method will be called with the additional lines of content.

+ +

tryContinue()

+ +
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue;
+
+ +

This method allows you to try and parse an additional line of Markdown.

+ +

closeBlock()

+ +
public function closeBlock(): void;
+
+ +

This method is called when the block is done being parsed. Any final adjustments to the block should be made at this time.

+ +

parseInlines()

+ +
public function parseInlines(InlineParserEngineInterface $inlineParser): void;
+
+ +

This method is called when the engine is ready to parse any inline child elements.

+ +

Note: For performance reasons, this method is not part of BlockContinueParserInterface. If your block may contain inlines, you should make sure that your “continue parser” also implements BlockContinueParserWithInlinesInterface.

+ +

Tips

+ +

Here are some additional tips to consider when writing your own custom parsers:

+ +

Combining both into one file

+ +

Although parsing requires two classes, you can use the anonymous class feature of PHP to combine both into a single file! Here’s an example:

+ +
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
+use League\CommonMark\Parser\Block\BlockStartParserInterface;
+
+final class MyCustomBlockParser extends AbstractBlockContinueParser
+{
+    // TODO: implement your continuation parsing methods here
+
+    public static function createBlockStartParser(): BlockStartParserInterface
+    {
+        return new class implements BlockStartParserInterface
+        {
+            // TODO: implement the tryStart() method here
+        };
+    }
+}
+
+
+ +

Performance

+ +

The BlockStartParserInterface::tryStart() and BlockContinueParserInterface::tryContinue() methods may be called hundreds or thousands of times during execution. For best performance, have your methods return as early as possible, and make sure your code is highly optimized.

+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

If your block contains literal strings/text within the block (and not as part of a child block), you should have your custom block type also implement StringContainerInterface.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/configuration/index.html b/2.4/customization/configuration/index.html new file mode 100644 index 0000000000..6c029db2ea --- /dev/null +++ b/2.4/customization/configuration/index.html @@ -0,0 +1,510 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Configuration Schemas and Values

+ +

Version 2.0 introduced a new robust system for defining configuration schemas and accessing them within custom extensions.

+ +

Configuration Schemas

+ +

Unlike in 1.x, all configuration options must have a defined schema. This defines which options are available, what types of values they accept, whether any are required, and any default values you wish to define if the user doesn’t provide any.

+ +

These custom options can be defined from within your custom extension by implementing the ConfigurableExtensionInterface:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+use Nette\Schema\Expect;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        $builder->addSchema('my_extension', Expect::structure([
+            'enable_some_feature' => Expect::bool()->default(true),
+            'html_class' => Expect::string()->default('my-custom-extension'),
+            'align' => Expect::anyOf('left', 'center', 'right')->default('left'),
+            'favorite_number' => Expect::int()->min(1)->max(100)->default(42),
+        ]));
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        // TODO: Implement register() method
+    }
+}
+
+ +

See the league/config documentation for more examples of how to define custom configuration schemas.

+ +

Note that you only need to implement ConfigurableExtensionInterface if you plan to define new configuration options - you don’t need this if you’re only reading existing options.

+ +

Reading Configuration Values

+ +

Okay, so your extension has defined the different options that are available, but now you want to start using them within your custom extension. There are a few ways you can access the values:

+ +

During Extension Registration

+ +

Perhaps your extension needs to decide whether/how to register certain parsers/renderers/etc based on the user-provided configuration values - in that case, you can read the value from the $environment - for example:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        // (see code example above)
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        if ($environment->getConfiguration()->get('my_extension/enable_some_feature')) {
+            $environment->addBlockStartParser(new MyCustomParser());
+            $environment->addRenderer(MyCustomBlockType::class, new MyCustomRenderer());
+        }
+    }
+}
+
+ +

Within Parsers/Renderers/Listeners

+ +

Perhaps you want to reference those configuration values from within a custom parser, renderer, event listener, or something else. This can easily by done by having that class also implement ConfigurationAwareInterface. This interface signals to the Environment that your class needs a copy of the final configuration so it can read it later:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\Config\ConfigurationAwareInterface;
+use League\Config\ConfigurationInterface;
+
+final class MyCustomRenderer implements NodeRendererInterface, ConfigurationAwareInterface
+{
+    /**
+     * @var ConfigurationInterface
+     */
+    private $config;
+
+    public function setConfiguration(ConfigurationInterface $configuration): void
+    {
+        $this->config = $configuration;
+    }
+
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return 'My favorite number is ' . $this->config->get('my_extension/favorite_number');
+    }
+}
+
+ +

You can access any configuration value from here, not just the ones you might have defined yourself.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/cursor/index.html b/2.4/customization/cursor/index.html new file mode 100644 index 0000000000..d118dfe48d --- /dev/null +++ b/2.4/customization/cursor/index.html @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Parser\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter(int $index)Returns the character at the given absolute position
getCurrentCharacter()Returns the character at the current position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/delimiter-processing/index.html b/2.4/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..dfee97ee27 --- /dev/null +++ b/2.4/customization/delimiter-processing/index.html @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void;
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
+
+// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
use League\CommonMark\Delimiter\Delimiter;
+use League\CommonMark\Node\Inline\Text;
+
+$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/disabling-features/index.html b/2.4/customization/disabling-features/index.html new file mode 100644 index 0000000000..f0f3b93b92 --- /dev/null +++ b/2.4/customization/disabling-features/index.html @@ -0,0 +1,475 @@ + + + + + + + + + + + + + + + + + Disabling Features - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Disabling Features

+ +

The CommonMark parser is designed to be highly configurable. You can disable certain features that you don’t want to have in your application. There are a few ways to do this, depending on your needs:

+ +

Avoiding Parsing

+ +

You cannot disable an already-registered parser, but you can prevent it from being registered with +the Environment in the first place. This is exactly how the +InlinesOnlyExtension works - it’s a copy of the CommonMarkCoreExtension class but +with the parsers we don’t want removed.

+ +

You can mirror this approach by defining your own custom extension class that registers +only the specific parsers, renderers, etc. that you want.

+ +

The only potential downside to this approach is that any syntax for those disabled features will appear in the output. +For example, if you were to prevent block quotes from being parsed, then the following Markdown:

+ +
> This is a block quote
+
+ +

Would have the > character appear in the output HTML:

+ +
<p>&gt; This is a block quote</p>
+
+ +

This is probably fine for most use cases.

+ +

Removing Parsed Elements

+ +

An alternative approach is to keep the parser enabled, but remove the parsed elements from the AST before rendering.

+ +

You’d create an event listener +(sort of like this one) that will +iterate all parsed elements, locate the target nodes, and remove them +by calling $node->detach().

+ +

There are three potential advantages to this approach:

+ +
    +
  1. You don’t need to create a custom extension class or prevent parsers from being registered
  2. +
  3. You can selectively remove certain elements based on their properties (e.g. only remove heading levels 3-6) while keeping others
  4. +
  5. The syntax and contents of the removed elements will not appear in the output HTML
  6. +
+ +

The downside is that you still incur the overhead of parsing the elements that are eventually removed.

+ +

Override Rendering

+ +

The final approach is to keep the parser enabled, but override how the parsed elements are rendered. For example, +you could implement a custom renderer for certain elements that simply returns +something else (perhaps an empty string, or an HTML comment of <!-- REMOVED -->) instead of the HTML you don’t want.

+ +

This approach is not recommended because:

+ +
    +
  1. You still incur the overhead of parsing the elements that are eventually removed
  2. +
  3. You’d need to register your custom renderer with a higher priority than the default renderer
  4. +
  5. You’d need to repeat this for every renderer that could potentially render the elements you want to remove
  6. +
+ +

It should technically work though, if you really want to go this route.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/environment/index.html b/2.4/customization/environment/index.html new file mode 100644 index 0000000000..d90eacf996 --- /dev/null +++ b/2.4/customization/environment/index.html @@ -0,0 +1,508 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all extensions, parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

An empty Environment can be obtained like this:

+ +
use League\CommonMark\Environment\Environment;
+
+$config = [];
+$environment = new Environment($config);
+
+ +

You can customize the Environment using any of the methods below (from the EnvironmentBuilderInterface interface).

+ +

Once your Environment is configured with whatever configuration and extensions you want, you can instantiate a MarkdownConverter and start converting MD to HTML:

+ +
use League\CommonMark\MarkdownConverter;
+
+// Using $environment from the previous code sample
+$converter = new MarkdownConverter($environment);
+
+echo $converter->convert('# Hello World!');
+
+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. For example, if you want core CommonMark functionality plus footnote support:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+
+$config = [];
+$environment = new Environment($config);
+
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new FootnoteExtension());
+
+ +

addBlockStartParser()

+ +
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockStartParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addRenderer()

+ +
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0);
+
+ +

Registers a NodeRendererInterface to handle a specific type of AST node ($nodeClass) with the given priority (a higher number will be executed earlier).

+ +

See Rendering for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ +

Accessing the Environment and Configuration within parsers/renderers/etc

+ +

If your custom parser/renderer/listener/etc. implements either EnvironmentAwareInterface or ConfigurationAwareInterface we’ll automatically inject the environment or configuration into them once the environment has been fully initialized. This will provide your code with access to the finalized information it may need.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/event-dispatcher/index.html b/2.4/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..2affccd0a3 --- /dev/null +++ b/2.4/customization/event-dispatcher/index.html @@ -0,0 +1,582 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Event Dispatcher

+ +

This library includes basic, PSR-14-compliant event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code.

+ +

Event Class

+ +

Any PSR-14 compliant event can be used, though we also provide an AbstractEvent class you can use to easily create your own events:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - the event object to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

League\CommonMark\Event\DocumentPreRenderEvent

+ +

This event is dispatched by the renderer just before rendering begins. Like with DocumentParsedEvent, this offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering, but with the added knowledge of which format is being rendered to (e.g. html).

+ +

League\CommonMark\Event\DocumentRenderedEvent

+ +

This event is dispatched once the rendering step has been completed, just before the output is returned. The final output can be adjusted at this point or additional metadata can be attached to the return object.

+ +

Bring Your Own PSR-14 Event Dispatcher

+ +

Although this library provides PSR-14 compliant event dispatching out-of-the-box, you may want to use your own PSR-14 event dispatcher instead. This is possible as long as that third-party library both:

+ +
    +
  1. Implements the PSR-14 EventDispatcherInterface; and,
  2. +
  3. Allows you to register additional ListenerProviderInterface instances with that dispatcher library
  4. +
+ +

Not all libraries support this so please check carefully! Assuming yours does, delegating all the event behavior to that library can be done with two steps:

+ +

First, call the setEventDispatcher() method on the Environment to register that other implementation. With that done, any calls to Environment::dispatch() will be passed through to that other dispatcher. But we still need to let that dispatcher know about the events registered by CommonMark extensions, otherwise nothing will happen when events are dispatched.

+ +

Because the Environment implements PSR-14’s ListenerProviderInterface you’ll also need to pass the configured Environment object to your event dispatcher so that it becomes aware of those available events.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event): void
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data->append('attributes/class', 'external-link');
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfiguration()->get('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = new Environment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convert($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/extensions/index.html b/2.4/customization/extensions/index.html new file mode 100644 index 0000000000..1f5f80883e --- /dev/null +++ b/2.4/customization/extensions/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a ConfigurableEnvironmentInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\Environment\ConfigurableEnvironmentInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(ConfigurableEnvironmentInterface $environment): void
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new EmojiExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/inline-parsing/index.html b/2.4/customization/inline-parsing/index.html new file mode 100644 index 0000000000..405bb47fd1 --- /dev/null +++ b/2.4/customization/inline-parsing/index.html @@ -0,0 +1,600 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getMatchDefinition()

+ +

This method should return an instance of InlineParserMatch which defines the text the parser is looking for. Examples of this might be something like:

+ +
use League\CommonMark\Parser\Inline\InlineParserMatch;
+
+InlineParserMatch::string('@');                  // Match any '@' characters found in the text
+InlineParserMatch::string('foo');                // Match the text 'foo' (case insensitive)
+
+InlineParserMatch::oneOf('@', '!');              // Match either character
+InlineParserMatch::oneOf('http://', 'https://'); // Match either string
+
+InlineParserMatch::regex('\d+');                 // Match the regular expression (omit the regex delimiters and any flags)
+
+ +

Once a match is found, the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has found at a matching string in the current line; and,
  2. +
  3. No other inline parsers with a higher priority have successfully parsed the text at this point in the line
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser - see more information below.
  • +
+ +
InlineParserContext
+ +

This class has several useful methods:

+ +
    +
  • getContainer() - Returns the current container block the inline text was found in. You’ll almost always call $inlineContext->getContainer()->appendChild(...) to add the parsed inline text inside that block.
  • +
  • getReferenceMap() - Returns the document’s reference map
  • +
  • getCursor() - Returns the current Cursor used to parse the current line. (Note that the cursor will be positioned before the matched text, so you must advance it yourself if you determine it’s a valid match)
  • +
  • getDelimiterStack() - Returns the current delimiter stack. Only used in advanced use cases.
  • +
  • getFullMatch() - Returns the full string that matched you InlineParserMatch definition
  • +
  • getFullMatchLength() - Returns the length of the full match - useful for advancing the cursor
  • +
  • getSubMatches() - If your InlineParserMatch used a regular expression with capture groups, this will return the text matches by those groups.
  • +
  • getMatches() - Returns an array where index 0 is the “full match”, plus any sub-matches. It basically simulates preg_match()’s behavior.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the text at the current position for any reason. Other parsers will then have a chance to try parsing that text. If all registered parsers return false, the text will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed/matched text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::regex('@([A-Za-z0-9_]{1,15}(?!\w))');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+
+        // This seems to be a valid match
+        // Advance the cursor to the end of the match
+        $cursor->advanceBy($inlineContext->getFullMatchLength());
+
+        // Grab the Twitter handle
+        [$handle] = $inlineContext->getSubMatches();
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+// And here's how to hook it up:
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::oneOf(':)', ':(');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($inlineContext->getFullMatch() === ':)') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($inlineContext->getFullMatch() === ':(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance: +
      +
    • Avoid using overly-complex regular expressions in getMatchDefinition() - use the simplest regex you can and have parse() do the heavier validation
    • +
    • Have your parse() method return false as soon as possible.
    • +
    +
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/overview/index.html b/2.4/customization/overview/index.html new file mode 100644 index 0000000000..0f36d6e0d9 --- /dev/null +++ b/2.4/customization/overview/index.html @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders/configuration you need
  2. +
  3. Instantiate a MarkdownParser and HtmlRenderer using that Environment
  4. +
  5. Use the MarkdownParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  6. +
  7. Use the HtmlRenderer to convert the AST Document into HTML
  8. +
+ +

The MarkdownConverter class handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Renderer\HtmlRenderer;
+
+$environment = new Environment([
+    'html_input' => 'strip',
+]);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderDocument($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renderers convert the parsed blocks/inlines from the AST representation into HTML. When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/rendering/index.html b/2.4/customization/rendering/index.html new file mode 100644 index 0000000000..f5a728ffed --- /dev/null +++ b/2.4/customization/rendering/index.html @@ -0,0 +1,564 @@ + + + + + + + + + + + + + + + + + Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Custom Rendering

+ +

Renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement NodeRendererInterface and its render() method. Note that in v2.0, both +block renderers and inline renderers share the same interface and method:

+ +

render()

+ +
public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The HtmlRenderer will call this method during the rendering process whenever a supported element is encountered.

+ +

If your renderer can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • Node $node - The encountered block or inline element that needs to be rendered
  • +
  • ChildNodeRendererInterface $childRenderer - If the given $node has children, use this to render those child elements
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the node and its contents, including any children. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\Util\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Renderers

+ +

When registering your renderer, you must tell the Environment which node element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// First param - the node class type that should use our renderer
+// Second param - instance of the renderer
+$environment->addRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple types:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
+use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements NodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(ThematicBreak::class, new TextDividerRenderer());
+
+ +

Note that thematic breaks should not contain children, which is why the $childRenderer is unused in this example. Otherwise we’d have to call code like this and return the result as part of the rendered HTML we’re generating here: $innerHtml = $childRenderer->renderNodes($node->children());

+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any child elements that your node might contain!
  • +
+ +

Wrapping Elements with HtmlDecorator

+ +

A utility class called HtmlDecorator is provided to make it easier to wrap the output of any renderer within an additional HTML tag with custom attributes and/or classes. To use it:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Renderer\HtmlDecorator;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\Extension\Table\TableRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(Table::class, new HtmlDecorator(new TableRenderer(), 'div', ['class' => 'table-responsive']));
+
+ +

XML Rendering

+ +

The XML renderer will automatically attempt to convert any AST nodes to XML by inspecting the name of the block/inline node and its attributes. You can instead control the XML element name and attributes by making your renderer implement XmlNodeRendererInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+use League\CommonMark\Xml\XmlNodeRendererInterface;
+
+class TextDividerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+
+    public function getXmlTagName(Node $node): string
+    {
+        return 'text_divider';
+    }
+
+    public function getXmlAttributes(Node $node): array
+    {
+        return ['character' => '='];
+    }
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/customization/slug-normalizer/index.html b/2.4/customization/slug-normalizer/index.html new file mode 100644 index 0000000000..3eb5d43330 --- /dev/null +++ b/2.4/customization/slug-normalizer/index.html @@ -0,0 +1,509 @@ + + + + + + + + + + + + + + + + + Slug Normalizer - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Slug Normalizer

+ +

“Slugs” are strings used within href, name, and id HTML attributes to identify particular elements within a document.

+ +

Some extensions (like the HeadingPermalinkExtension) need the ability to convert user-provided text into these URL-safe slugs while also ensuring that these are unique throughout the generated HTML. The Environment provides a pre-built normalizer you can use for this purpose.

+ +

Usage

+ +

You can obtain a reference to the built-in slug normalizer by calling $environment->getSlugNormalizer();

+ +

To use this within your extension, have your parser/renderer/whatever implement EnvironmentAwareInterface and then implement the corresponding setEnvironment method like this:

+ +

+use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Environment\EnvironmentAwareInterface;
+
+class MyCustomParserOrRenderer implements EnvironmentAwareInterface
+{
+    private $slugNormalizer;
+
+    public function setEnvironment(EnvironmentInterface $environment): void
+    {
+        $this->slugNormalizer = $environment->getSlugNormalizer();
+    }
+}
+
+ +

You can then call $this->slugNormalizer->normalize($text) as needed.

+ +

Configuration

+ +

The slug_normalizer configuration section allows you to adjust the following options:

+ +

instance

+ +

You can change the string that is used as the “slug” by setting the instance option to any class that implements TextNormalizerInterface. +We provide a simple SlugNormalizer by default, but you may want to plug in a different library or create your own normalizer instead.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

max_length

+ +

This can be configured to limit the length of that slug to prevent overly-long values. By default, that limit is 255 characters. You may set this to any positive integer, or 0 for no limit.

+ +

(Note that generated slugs might be slightly longer than this “limit” if the unique option is enabled and the slug generator detects a duplicate slug and needs to add a suffix to make it unique.)

+ +

unique

+ +

This options controls whether slugs should be unique. Possible values include:

+ +
    +
  • 'document' (string; default) - Ensures slugs are unique within a single document
  • +
  • 'environment' (string) - Ensures slugs are unique across multiple documents - see below
  • +
  • false (boolean) - Disables unique slug generation
  • +
+ +

You might have a use case where you’re converting several different Markdown documents on the same page and so you’d like to ensure that none of those documents use conflicting slugs. In that case, you should set the scope option to 'environment' to ensure that a single instance of a MarkdownConverter (which uses a single Environment) will never produce the same slug twice during its lifetime (which usually lasts the entire duration of a single HTTP request).

+ +

If you need complete control over how unique slugs are generated, make your 'instance' implement UniqueSlugNormalizerInterface; otherwise, we’ll simply append incremental numbers to slugs to ensure they are unique.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/attributes/index.html b/2.4/extensions/attributes/index.html new file mode 100644 index 0000000000..c85e394da4 --- /dev/null +++ b/2.4/extensions/attributes/index.html @@ -0,0 +1,490 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+ +

This results in the following output:

+ +
<blockquote title="Blockquote title">
+<p>A nice blockquote</p>
+</blockquote>
+
+ +

CSS-selector-style declarations can be used to set the id and class attributes:

+ +
{#id .class}
+## Header
+
+ +

Output:

+ +
<h2 class="class" id="id">Header</h2>
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Output:

+ +
<p>This is <em style="color: red">red</em>.</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/autolinks/index.html b/2.4/extensions/autolinks/index.html new file mode 100644 index 0000000000..48098a241c --- /dev/null +++ b/2.4/extensions/autolinks/index.html @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a Mention Parser outside of this extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/commonmark/index.html b/2.4/extensions/commonmark/index.html new file mode 100644 index 0000000000..27ee9fef97 --- /dev/null +++ b/2.4/extensions/commonmark/index.html @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically installed for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new Environment with the core extension
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/default-attributes/index.html b/2.4/extensions/default-attributes/index.html new file mode 100644 index 0000000000..597790430f --- /dev/null +++ b/2.4/extensions/default-attributes/index.html @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + Default Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Default Attributes

+ +

The DefaultAttributesExtension allows you to apply default HTML classes and other attributes using configuration options.

+ +

It works by applying the attributes to the nodes during the DocumentParsedEvent event - right after the nodes are parsed but before they are rendered. +(As a result, it’s possible that renderers may add other attributes - the goal of this extension is only to provide custom defaults.)

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DefaultAttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Node\Block\Paragraph;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'default_attributes' => [
+        Heading::class => [
+            'class' => static function (Heading $node) {
+                if ($node->getLevel() === 1) {
+                    return 'title-main';
+                } else {
+                    return null;
+                }
+            },
+        ],
+        Table::class => [
+            'class' => 'table',
+        ],
+        Paragraph::class => [
+            'class' => ['text-center', 'font-comic-sans'],
+        ],
+        Link::class => [
+            'class' => 'btn btn-link',
+            'target' => '_blank',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new DefaultAttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a default_attributes array. Each key in the array should be a FQCN for the node class you wish to apply the default attribute to, and the values should be a map of attribute names to attribute values.

+ +

Attribute values may be any of the following types:

+ +
    +
  • string
  • +
  • string[]
  • +
  • bool
  • +
  • callable (parameter is the Node, return value may be string|string[]|bool)
  • +
+ +

Examples

+ +

Here’s an example that will apply Bootstrap 4 classes and attributes:

+ +
$config = [
+    'default_attributes' => [
+        Table::class => [
+            'class' => ['table', 'table-responsive'],
+        ],
+        BlockQuote::class => [
+            'class' => 'blockquote',
+        ],
+    ],
+];
+
+ +

Here’s a more complex example that uses a callable to add a class only if the paragraph immediately follows an <h1> heading:

+ +
$config = [
+    'default_attributes' => [
+        Paragraph::class => [
+            'class' => static function (Paragraph $paragraph) {
+                if ($paragraph->previous() instanceof Heading && $paragraph->previous()->getLevel() === 1) {
+                    return 'lead';
+                }
+
+                return null;
+            },
+        ],
+    ],
+];
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/description-lists/index.html b/2.4/extensions/description-lists/index.html new file mode 100644 index 0000000000..b118bd451f --- /dev/null +++ b/2.4/extensions/description-lists/index.html @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + Description List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Description List Extension

+ +

The DescriptionListExtension adds Markdown Extra-style description lists to facilitate the creation of <dl>, <dt>, and <dd> HTML using Markdown.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DescriptionListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\DescriptionList\DescriptionListExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DescriptionListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some markdown goes here');
+
+ +

Syntax

+ +

The syntax is based directly on the rules and logic implemented by the Markdown Extra library. Here are some examples of sample Markdown input and HTML output demonstrating the syntax:

+ +
Apple
+:   Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.
+:   An American computer company.
+
+Orange
+:   The fruit of an evergreen tree of the genus Citrus.
+
+ +
<dl>
+    <dt>Apple</dt>
+    <dd>Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.</dd>
+    <dd>An American computer company.</dd>
+
+    <dt>Orange</dt>
+    <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
+</dl>
+
+ +

See the Markdown Extra documentation or our own spec for additional examples.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/disallowed-raw-html/index.html b/2.4/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..7b20b99dd7 --- /dev/null +++ b/2.4/extensions/disallowed-raw-html/index.html @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically escapes certain HTML tags when rendering raw HTML, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Customize the extension's configuration if needed
+// Default values are shown below - you can omit this configuration if you're happy with those defaults
+// and don't want to customize them
+$config = [
+    'disallowed_raw_html' => [
+        'disallowed_tags' => ['title', 'textarea', 'style', 'xmp', 'iframe', 'noembed', 'noframes', 'script', 'plaintext'],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I cannot change the page <title>anymore</title>');
+
+ +

Configuration

+ +

This extension can be configured by providing a disallowed_raw_html array with the following nested configuration options. The defaults are shown in the code example above.

+ +

disallowed_tags

+ +

An array containing a list of tags that should be escaped.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/embed/index.html b/2.4/extensions/embed/index.html new file mode 100644 index 0000000000..e9ee50d60c --- /dev/null +++ b/2.4/extensions/embed/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + Embed Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Embed Extension

+ +

This extension can embed rich content (like videos, tweets, etc.) from other websites.

+ +

The syntax is very simple - simply place any https:// URL on its own line like this:

+ +
Check out this video!
+
+https://www.youtube.com/watch?v=dQw4w9WgXcQ
+
+ +

If the link points to embeddable content, it will be replaced with the rich HTML needed to embed it:

+ +
<p>Check out this video:</p>
+<iframe width="200" height="113" src="https://www.youtube.com/embed/dQw4w9WgXcQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

You’ll also need to install a third-party OEmbed library - see the Adapter section below.

+ +

Usage

+ +

Configure your Environment as usual and add the EmbedExtension provided by this package:

+ +
use Embed\Embed;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Embed\EmbedExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'embed' => [
+        'adapter' => new OscaroteroEmbedAdapter(), // See the "Adapter" documentation below
+        'allowed_domains' => ['youtube.com', 'twitter.com', 'github.com'],
+        'fallback' => 'link',
+    ],
+];
+
+// Configure the Environment with all whatever other extensions you want
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new EmbedExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
+
+ +

Configuration

+ +

This extension supports the following configuration options under the embed configuration:

+ +

adapter option

+ +

Any instance of EmbedAdapterInterface - see the “Adapter” section below.

+ +

allowed_domains option

+ +

This option defines a list of hosts that you wish to allow embedding content from. For example, setting this to +['youtube.com'] would only allow videos from YouTube to be embedded. +It’s extremely important that you only include websites you trust since they’ll be providing HTML that is directly embedded in your website.

+ +

Any subdomains of these domains will also be allowed. For example, ['youtube.com'] would allow embedding from youtube.com or www.youtube.com.

+ +

As an additional safety measure, we recommend that you also use a Content Security Policy (CSP) +to prevent unexpected content from being embedded.

+ +

By default, this option is an empty array ([]), which means that all domains are allowed.

+ +

fallback option

+ +

This options defines the behavior when a URL cannot be embedded, either because it’s not in the list of allowed_domains, +or because the adapter could not find embeddable content for that URL.

+ +

There are two possible values for this option:

+ +
    +
  • 'link' - the URL will be kept in the document as a link (default) +-'remove' - the URL will be completely removed from the document
  • +
+ +

Adapter

+ +

league/commonmark doesn’t know how to obtain the embeddable HTML for a given URL - this must be done by an external library.

+ +

embed/embed Adapter

+ +

We do provide an adapter for the popular embed/embed library. if you’d like to use that. We like this library +because it supports fetching multiple URLs in parallel, which is ideal for performance, and it supports a wide range +of embeddable content.

+ +

To use that library, you’ll need to composer require embed/embed and then pass new OscaroteroEmbedAdapter() as the adapter +configuration option, as shown in the Usage section above.

+ +

Need to customize the maximum width/height of the embedded content? You can do that by instantiating the service provided by +embed/embed, configuring it as needed, and passing that customized instance into the adapter:

+ +
use Embed\Embed;
+use League\CommonMark\Extension\Embed\Bridge\OscaroteroEmbedAdapter;
+
+// Configure the Embed library itself
+$embedLibrary = new Embed();
+$embedLibrary->setSettings([
+    'oembed:query_parameters' => [
+        'maxwidth' => 800,
+        'maxheight' => 600,
+    ],
+    'twitch:parent' => 'example.com',
+    'facebook:token' => '1234|5678',
+    'instagram:token' => '1234|5678',
+    'twitter:token' => 'asdf',
+]);
+
+// Inject it into our adapter
+$config = [
+    'adapter' => new OscaroteroEmbedAdapter($embedLibrary),
+];
+
+// Instantiate your CommonMark environment and converter like usual
+// ...
+
+ +

Custom Adapter

+ +

If you prefer to use a different library, you’ll need to implement our EmbedAdapterInterface yourself with +whatever OEmbed library you choose.

+ +

Tips

+ +

If you need to wrap the HTML in a container tag, consider using the HtmlDecorator renderer:

+ +
$environment->addRenderer(Embed::class, new HtmlDecorator(new EmbedRenderer(), 'div', ['class' => 'embeded-content']));
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/external-links/index.html b/2.4/extensions/external-links/index.html new file mode 100644 index 0000000000..a81d062f97 --- /dev/null +++ b/2.4/extensions/external-links/index.html @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class MyCustomLinkRenderer implements NodeRendererInterface
+{
+    /**
+     * @param Node                       $node
+     * @param ChildNodeRendererInterface $childRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        Link::assertInstanceOf($node);
+
+        if ($node->data->get('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to automagically add an external link icon by targeting the html_class given in the configuration:

+ +
// Font Awesome example:
+a[target="_blank"]::after,
+a.external::after {
+   content: "\f08e";
+   font: normal normal normal 14px/1 FontAwesome;
+}
+
+// Glyphicon example:
+a[target="_blank"]::after,
+a.external::after {
+  @extend .glyphicon;
+  content: "\e164";
+  margin-left: .5em;
+  margin-right: .25em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/footnotes/index.html b/2.4/extensions/footnotes/index.html new file mode 100644 index 0000000000..d36533977a --- /dev/null +++ b/2.4/extensions/footnotes/index.html @@ -0,0 +1,564 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1"></a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'backref_symbol'     => '↩',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

backref_symbol

+ +

This string option sets the symbol used as the contents of the footnote backreference link. It defaults to \League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer::DEFAULT_SYMBOL = '↩'.

+ +

If you want to use a custom icon, set this to an empty string '' and take a look at the Adding Icons section below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ +

Adding Icons

+ +

You can use CSS to add a custom icon instead of providing a backref_symbol:

+ +
$config = [
+    'footnote' => [
+        'backref_class' => 'footnote-backref',
+        'backref_symbol' => '',
+    ],
+];
+
+ +

Then target the backref_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.footnote-backref::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/front-matter/index.html b/2.4/extensions/front-matter/index.html new file mode 100644 index 0000000000..96b26eca10 --- /dev/null +++ b/2.4/extensions/front-matter/index.html @@ -0,0 +1,550 @@ + + + + + + + + + + + + + + + + + Front Matter Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Front Matter Extension

+ +

The FrontMatterExtension adds the ability to parse YAML front matter from the Markdown document and include that in the return result.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

You will also need to install symfony/yaml or the YAML extension for PHP to use this extension. For symfony/yaml:

+ +
composer require symfony/yaml
+
+ +

(You can use any version of symfony/yaml 2.4 or higher, though we recommend using 4.0 or higher.)

+ +

Front Matter Syntax

+ +

This extension follows the Jekyll Front Matter syntax. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Here is a basic example:

+ +
---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+
+ +

This will produce a front matter array similar to this:

+ +
$parsedFrontMatter = [
+    'layout' => 'post',
+    'title' => 'I Love Markdown',
+    'tags' => [
+        'test',
+        'example',
+    ],
+];
+
+ +

And the HTML output will only contain the one heading:

+ +
<h1>Hello World!</h1>
+
+ +

Usage

+ +

Configure your Environment as usual and add the FrontMatterExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FrontMatterExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+// A sample Markdown file with some front matter:
+$markdown = <<<MD
+---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+MD;
+
+$result = $converter->convert($markdown);
+
+// Grab the front matter:
+if ($result instanceof RenderedContentWithFrontMatter) {
+    $frontMatter = $result->getFrontMatter();
+}
+
+// Output the HTML using any of these:
+echo $result;               // implicit string cast
+// or:
+echo (string) $result;      // explicit string cast
+// or:
+echo $result->getContent();
+
+ +

Parsing Front Matter Only

+ +

You don’t have to parse the entire file (including all the Markdown) if you only want the front matter. You can either instantiate the front matter parser yourself and call it directly, like this:

+ +
use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+// For `symfony/yaml`
+$frontMatterParser = new FrontMatterParser(new SymfonyYamlFrontMatterParser());
+// For YAML extension
+$frontMatterParser = new FrontMatterParser(new LibYamlFrontMatterParser());
+$result = $frontMatterParser->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

Or you can use the getFrontMatterParser() method from the extension:

+ +
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+$frontMatterExtension = new FrontMatterExtension();
+$result = $frontMatterExtension->getFrontMatterParser()->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

This latter approach may be more convenient if you have already instantiated a FrontMatterExtension object you’re adding to the Environment somewhere and just want to call that.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/github-flavored-markdown/index.html b/2.4/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..ee8df59b11 --- /dev/null +++ b/2.4/extensions/github-flavored-markdown/index.html @@ -0,0 +1,475 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark and GFM parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/heading-permalinks/index.html b/2.4/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..3dc74fd7e3 --- /dev/null +++ b/2.4/extensions/heading-permalinks/index.html @@ -0,0 +1,633 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\MarkdownConverter;
+
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'content',
+        'apply_id_to_heading' => false,
+        'heading_class' => '',
+        'fragment_prefix' => 'content',
+        'insert' => 'before',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+        'aria_hidden' => true,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

apply_id_to_heading

+ +

If this value is true, the id attributes will be written to the <h> tag instead of the <a>.

+ +

heading_class

+ +

The class will be added to the <h> tag (no matter if apply_id_to_heading is set true or false)

+ +

fragment_prefix

+ +

This should be a string you want prepended to the URL fragment in the link’s href attribute. This should typically be set to the same value as id_prefix for links to work properly. However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to “rewrite” them.

+ +

For example, to emulate how GitHub heading permalinks work, set id_prefix to 'user-content', set fragment_prefix to '', and insert some JavaScript into the page like this:

+ +
var scrollToPermalink = function() {
+    var link = document.getElementById('user-content-' + window.location.hash);
+    if (link) {
+        link.scrollIntoView({behavior: 'smooth'});
+    }
+};
+
+window.addEventListener('hashchange', scrollToPermalink);
+if (window.location.hash) {
+    scrollToPermalink();
+}
+
+ +

insert

+ +

This controls whether the anchor is added to the beginning of the heading tag (before), the end of the tag (after), or not added at all (none).

+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should have permalinks added. By default, all 6 levels (1, 2, 3, 4, 5, and 6) will have them. You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

aria_hidden

+ +

This option sets the aria-hidden attribute on the <a> tag. This defaults to aria-hidden="true".

+ +

Setting this option to false would render the <a> tag excluding the aria-hidden entirely.

+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/inlines-only/index.html b/2.4/extensions/inlines-only/index.html new file mode 100644 index 0000000000..ad461d12a5 --- /dev/null +++ b/2.4/extensions/inlines-only/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new, empty environment
+$environment = new Environment($config);
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/mentions/index.html b/2.4/extensions/mentions/index.html new file mode 100644 index 0000000000..0a807877ad --- /dev/null +++ b/2.4/extensions/mentions/index.html @@ -0,0 +1,674 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which prefix you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting prefix, a regular expression to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same prefix, the first mention parser to
+        // successfully match and return a properly constructed Mention object (where the URL has been set) will be the
+        // the mention parser that is used. In this example, the GitHub handle would actually match first because
+        // there isn't any real validation to check whether https://www.github.com/colinodell exists. However, in
+        // CMS applications, you could check whether its a local user first, then check Twitter and then GitHub, etc.
+        'twitter_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[A-Za-z0-9_]{1,15}(?!\w)',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on GitHub: @colinodell');
+// Output:
+// <p>Follow me on GitHub: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Prefix └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the prefix (in this case, @). You can use any prefix, regular expression pattern (without opening/closing delimiter or modifiers), or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Node\Inline\AbstractInline;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Inline\Element\AbstractInline;
+
+class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'user_url_generator' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z0-9]+',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/overview/index.html b/2.4/extensions/overview/index.html new file mode 100644 index 0000000000..5e6026f570 --- /dev/null +++ b/2.4/extensions/overview/index.html @@ -0,0 +1,606 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Default AttributesEasily apply default HTML classes using configuration options to match your site’s styles2.0.0 
Description ListsCreate <dl> description lists using Markdown Extra’s syntax2.0.0 
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
EmbedEmbed rich content (like videos, tweets, and more) from other websites2.3.0 
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
Front MatterParses YAML front matter from your Markdown input2.0.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the extensions you need
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the other extensions you need
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/smart-punctuation/index.html b/2.4/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..5f9f6495c6 --- /dev/null +++ b/2.4/extensions/smart-punctuation/index.html @@ -0,0 +1,467 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/strikethrough/index.html b/2.4/extensions/strikethrough/index.html new file mode 100644 index 0000000000..5e9c55e485 --- /dev/null +++ b/2.4/extensions/strikethrough/index.html @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/table-of-contents/index.html b/2.4/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..3cdf3aba3c --- /dev/null +++ b/2.4/extensions/table-of-contents/index.html @@ -0,0 +1,605 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/tables/index.html b/2.4/extensions/tables/index.html new file mode 100644 index 0000000000..f49c6280ac --- /dev/null +++ b/2.4/extensions/tables/index.html @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => false,
+            'tag' => 'div',
+            'attributes' => [],
+        ],
+        'alignment_attributes' => [
+            'left'   => ['align' => 'left'],
+            'center' => ['align' => 'center'],
+            'right'  => ['align' => 'right'],
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Configuration

+ +

Wrapping Container

+ +

You can “wrap” the table with a container element by configuring the following options:

+ +
    +
  • enabled: (boolean) Whether to wrap the table with a container element. Defaults to false.
  • +
  • tag: (string) The tag name of the container element. Defaults to div.
  • +
  • attributes: (array) An array of attributes to apply to the container element. Defaults to [].
  • +
+ +

For example, to wrap all tables within a <div class="table-responsive"> container element:

+ +
$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => true,
+            'tag' => 'div',
+            'attributes' => ['class' => 'table-responsive'],
+        ],
+    ],
+];
+
+ +

Alignment

+ +

You can configure the HTML attributes used for alignment via the alignment_attributes option. For example, if you wanted Bootstrap classes to be applied, you could do so like this:

+ +
$config = [
+    'table' => [
+        'alignment_attributes' => [
+            'left' => ['class' => 'text-start'],
+            'center' => ['class' => 'text-center'],
+            'right' => ['class' => 'text-end'],
+        ],
+    ],
+];
+
+ +

Or you could use inline styles:

+ +
$config = [
+    'table' => [
+        'alignment_attributes' => [
+            'left' => ['style' => 'text-align:left'],
+            'center' => ['style' => 'text-align:center'],
+            'right' => ['style' => 'text-align:right'],
+        ],
+    ],
+];
+
+ +

Or any other HTML attributes you’d like!

+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/extensions/task-lists/index.html b/2.4/extensions/task-lists/index.html new file mode 100644 index 0000000000..891478b616 --- /dev/null +++ b/2.4/extensions/task-lists/index.html @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convert($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/index.html b/2.4/index.html new file mode 100644 index 0000000000..1841692b31 --- /dev/null +++ b/2.4/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello, World!')->getContent();
+
+// <h1>Hello, World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/installation/index.html b/2.4/installation/index.html new file mode 100644 index 0000000000..a2451e227b --- /dev/null +++ b/2.4/installation/index.html @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Installation

+ +

The recommended installation method is via Composer.

+ +
composer require "league/commonmark:^2.4"
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^2.4. This is equivalent to >=2.4 <3.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/security/index.html b/2.4/security/index.html new file mode 100644 index 0000000000..6b209a4efd --- /dev/null +++ b/2.4/security/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convert($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/support/index.html b/2.4/support/index.html new file mode 100644 index 0000000000..f6efa4fdc1 --- /dev/null +++ b/2.4/support/index.html @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + + + Support - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Support

+ +

Here are some useful resources to help you use this project:

+ + + +

Supported Versions

+ +

See our security policy for information about the support cycle for bug fixes and security updates.

+ +

Reporting a Vulnerability

+ +

If you discover a security vulnerability within this package, please use the Tidelift security contact form or email Colin O’Dell at colinodell@gmail.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/upgrading/index.html b/2.4/upgrading/index.html new file mode 100644 index 0000000000..f92f207ce5 --- /dev/null +++ b/2.4/upgrading/index.html @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + Upgrading from 2.3 to 2.4 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

Upgrading from 2.3 to 2.4

+ +

Exception Changes

+ +

Prior to 2.4.0, this library did a poor job of using appropriate exception types and documenting which exceptions could +be thrown. For example, all of the main interfaces said that only RuntimeException could be thrown, but in reality +other exceptions like LogicException or InvalidArgumentException could be thrown in some cases!

+ +

This inconsistent behavior and inaccurate documentation has been fixed in 2.4.0 by:

+ +
    +
  • Adding a new CommonMarkException interface implemented by all exceptions thrown by this library
  • +
  • Adding several new exception types that implement that interface while also extending from the same base exception +type as that would have been previously thrown.
  • +
  • Fixing incorrect docblocks about the exception types being thrown
  • +
+ +

If you were previously catching exceptions thrown by this library in your code, you should consider changing your +catch blocks to either catch CommonMarkException (for all exceptions) or one of the exception types under the +League\CommonMark\Exception namespace.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.4/xml/index.html b/2.4/xml/index.html new file mode 100644 index 0000000000..f6601302e7 --- /dev/null +++ b/2.4/xml/index.html @@ -0,0 +1,454 @@ + + + + + + + + + + + + + + + + + XML Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + +

This is the documentation for version 2.4. Please consider upgrading your code to the latest stable version

+ + + + +

XML Rendering

+ +

Version 2.0 introduced the ability to render Markdown Document objects in XML. This is particularly useful for debugging custom extensions as you can see the XML representation of the Abstract Syntax Tree.

+ +

To convert Markdown to XML, you would instantiate a MarkdownToXmlConverter with an Environment and then call convert() on any Markdown.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Xml\MarkdownToXmlConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$converter = new MarkdownToXmlConverter($environment);
+
+echo $converter->convert('# **Hello** World!');
+
+ +

This will display XML output like this:

+ +
<?xml version="1.0" encoding="UTF-8"?>
+<document xmlns="http://commonmark.org/xml/1.0">
+    <heading level="1">
+        <strong>
+            <text>Hello</text>
+        </strong>
+        <text> World!</text>
+    </heading>
+</document>
+
+ +

Alternatively, if you already have a Document object you want to visualize in XML, you can use theXmlRenderer class to convert it to XML.

+ +

Return Value

+ +

Like with CommonMarkConverter::convert(), the renderDocument() actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final XML output.

+ +

Customizing the XML Output

+ +

See the rendering documentation for information on customizing the XML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/basic-usage/index.html b/2.5/basic-usage/index.html new file mode 100644 index 0000000000..a26e7226a7 --- /dev/null +++ b/2.5/basic-usage/index.html @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + Basic Usage - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Basic Usage

+ +

+Important: See the security section for important details on avoiding security misconfigurations.

+ +

The CommonMarkConverter class provides a simple wrapper for converting Markdown to HTML:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Or if you want GitHub-Flavored Markdown:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new GithubFlavoredMarkdownConverter();
+echo $converter->convert('# Hello World!');
+
+// <h1>Hello World!</h1>
+
+ +

Using Extensions

+ +

The CommonMarkConverter and GithubFlavoredMarkdownConverter shown above automatically configure the environment for you, but if you want to use additional extensions you’ll need to avoid those classes and use the generic MarkdownConverter class instead to customize the environment with whatever extensions you wish to use:

+ +
require __DIR__ . '/vendor/autoload.php';
+
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+
+$environment->addExtension(new InlinesOnlyExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+// <p><strong>Hello World!</strong></p>
+
+ +

Configuration

+ +

If you’re using the CommonMarkConverter or GithubFlavoredMarkdownConverter class you can pass configuration options directly into their constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

See the configuration section for more information on the available configuration options.

+ +

Supported Character Encodings

+ +

Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.

+ +

Return Value

+ +

The convert() method actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final HTML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/changelog/index.html b/2.5/changelog/index.html new file mode 100644 index 0000000000..040c08c262 --- /dev/null +++ b/2.5/changelog/index.html @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + Changelog - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Changelog

+ +

All notable changes made in 2.x releases are shown below. See the full list of releases for the complete changelog.

+ +

2.5.3 - 2024-08-16

+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.1, including: +
      +
    • Remove source, add search to list of recognized block tags
    • +
    +
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.2…2.5.3

+ +

2.5.2 - 2024-08-14

+ +

Changed

+ +
    +
  • Boolean attributes now require an explicit true value (#1040)
  • +
+ +

Fixed

+ +
    +
  • Fixed regression where text could be misinterpreted as an attribute (#1040)
  • +
+ +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.1…2.5.2

+ +

2.5.1 - 2024-07-24

+ +

Fixed

+ +
    +
  • Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
  • +
  • Fixed incorrect Table start line numbers (#1037)
  • +
+ +

New Contributors

+
    +
  • @jasonvarga made their first contribution in https://github.com/thephpleague/commonmark/pull/1035
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.0…2.5.1

+ +

2.5.0 - 2024-07-22

+ +

Added

+ +
    +
  • The AttributesExtension now supports attributes without values (#985, #986)
  • +
  • The AutolinkExtension exposes two new configuration options to override the default behavior (#969, #987): +
      +
    • autolink/allowed_protocols - an array of protocols to allow autolinking for
    • +
    • autolink/default_protocol - the default protocol to use when none is specified
    • +
    +
  • +
+ +

Changed

+ +
    +
  • Made compatible with CommonMark spec 0.31.0, including: +
      +
    • Allow closing fence to be followed by tabs
    • +
    • Remove restrictive limitation on inline comments
    • +
    • Unicode symbols now treated like punctuation (for purposes of flankingness)
    • +
    • Trailing tabs on the last line of indented code blocks will be excluded
    • +
    • Improved HTML comment matching
    • +
    +
  • +
  • Paragraphs only containing link reference definitions will be kept in the AST until the Document is finalized +
      +
    • (These were previously removed immediately after parsing the Paragraph)
    • +
    +
  • +
+ +

Fixed

+ +
    +
  • Fixed list tightness not being determined properly in some edge cases
  • +
  • Fixed incorrect ending line numbers for several block types in various scenarios
  • +
  • Fixed lowercase inline HTML declarations not being accepted
  • +
+ +

New Contributors

+
    +
  • @svenluijten made their first contribution in https://github.com/thephpleague/commonmark/pull/986
  • +
+ +

Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.4…2.5.0

+ +

Older Versions

+ +

Please see the full list of releases for the complete changelog.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/configuration/index.html b/2.5/configuration/index.html new file mode 100644 index 0000000000..08d6d20c63 --- /dev/null +++ b/2.5/configuration/index.html @@ -0,0 +1,513 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Configuration

+ +

Many aspects of this library’s behavior can be tweaked using configuration options.

+ +

You can provide an array of configuration options to the Environment or converter classes when creating them:

+ +
$config = [
+    'renderer' => [
+        'block_separator' => "\n",
+        'inner_separator' => "\n",
+        'soft_break'      => "\n",
+    ],
+    'commonmark' => [
+        'enable_em' => true,
+        'enable_strong' => true,
+        'use_asterisk' => true,
+        'use_underscore' => true,
+        'unordered_list_markers' => ['-', '*', '+'],
+    ],
+    'html_input' => 'escape',
+    'allow_unsafe_links' => false,
+    'max_nesting_level' => PHP_INT_MAX,
+    'slug_normalizer' => [
+        'max_length' => 255,
+    ],
+];
+
+ +

If you’re using the basic CommonMarkConverter or GithubFlavoredMarkdown classes, simply pass the configuration array into the constructor:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\GithubFlavoredMarkdownConverter;
+
+$converter = new CommonMarkConverter($config);
+// or
+$converter = new GithubFlavoredMarkdownConverter($config);
+
+ +

Otherwise, if you’re using MarkdownConverter to customize the extensions in your parser, pass the configuration into the Environment’s constructor instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Here's where we set the configuration array:
+$environment = new Environment($config);
+
+// TODO: Add any/all the extensions you wish; for example:
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Go forth and convert you some Markdown!
+$converter = new MarkdownConverter($environment);
+
+ +

Here’s a list of the core configuration options available:

+ +
    +
  • renderer - Array of options for rendering HTML +
      +
    • block_separator - String to use for separating renderer block elements
    • +
    • inner_separator - String to use for separating inner block contents
    • +
    • soft_break - String to use for rendering soft breaks
    • +
    +
  • +
  • html_input - How to handle HTML input. Set this option to one of the following strings: +
      +
    • strip - Strip all HTML (equivalent to 'safe' => true)
    • +
    • allow - Allow all HTML input as-is (default value; equivalent to `‘safe’ => false)
    • +
    • escape - Escape all HTML
    • +
    +
  • +
  • allow_unsafe_links - Remove risky link and image URLs by setting this to false (default: true)
  • +
  • max_nesting_level - The maximum nesting level for blocks (default: PHP_INT_MAX). Setting this to a positive integer can help protect against long parse times and/or segfaults if blocks are too deeply-nested.
  • +
  • slug_normalizer - Array of options for configuring how URL-safe slugs are created; see the slug normalizer docs for more details +
      +
    • instance - An alternative normalizer to use (defaults to the included SlugNormalizer)
    • +
    • max_length - Limits the size of generated slugs (defaults to 255 characters)
    • +
    • unique - Controls whether slugs should be unique per 'document' (default) or per 'environment'; can be disabled with false
    • +
    +
  • +
+ +

Additional configuration options are available for most of the available extensions - refer to their individual documentation for more details. For example, the CommonMark core extension offers these additional options:

+ +
    +
  • commonmark - Array of options for configuring the CommonMark core extension: +
      +
    • enable_em - Disable <em> parsing by setting to false; enable with true (default: true)
    • +
    • enable_strong - Disable <strong> parsing by setting to false; enable with true (default: true)
    • +
    • use_asterisk - Disable parsing of * for emphasis by setting to false; enable with true (default: true)
    • +
    • use_underscore - Disable parsing of _ for emphasis by setting to false; enable with true (default: true)
    • +
    • unordered_list_markers - Array of characters that can be used to indicate a bulleted list (default: ["-", "*", "+"])
    • +
    +
  • +
+ +

Environment

+ +

The configuration is ultimately passed to (and managed via) the Environment. If you’re creating your own Environment, simply pass your config array into its constructor instead.

+ +

Learn more about customizing the Environment

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/abstract-syntax-tree/index.html b/2.5/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..e0e0549441 --- /dev/null +++ b/2.5/customization/abstract-syntax-tree/index.html @@ -0,0 +1,708 @@ + + + + + + + + + + + + + + + + + Abstract Syntax Tree - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Abstract Syntax Tree

+ +

This library uses a doubly-linked list Abstract Syntax Tree (AST) to represent the parsed block and inline elements. All such elements extend from the Node class.

+ +

Document

+ +

The root node of the AST will always be a Document object. You can obtain this node a few different ways:

+ +
    +
  • By calling the parse() method on the MarkdownParser
  • +
  • By calling the getDocument() method on either the DocumentPreParsedEvent or DocumentParsedEvent see the (Event Dispatcher documentation)
  • +
+ +

Visualization

+ +

Even with an interactive debugger it can be tricky to view an entire tree at once. Consider using the XmlRenderer to provide a simple text-based representation of the AST for debugging purposes.

+ +

Node Traversal

+ +

There are four different ways to traverse/iterate the Nodes within the AST:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodProsCons
Manual TraversalBest way to access/check direct relatives of nodesNot useful for iteration
Iterating the TreeFast and efficientPossible unexpected behavior when adding/removing sibling nodes while iterating
Walking the TreeFull control over iterationUp to twice as slow as iteration; adding/removing nodes while iterating can lead to weird behaviors
Querying NodesEasier to write and understand; no weird behaviorsNot memory efficient
+ +

Each is described in more detail below

+ +

Manual Traversal

+ +

The following methods can be used to manually traverse from one Node to any of its direct relatives:

+ +
    +
  • previous()
  • +
  • next()
  • +
  • parent()
  • +
  • firstChild()
  • +
  • lastChild()
  • +
  • children()
  • +
+ +

This is best suited for situations when you need to know information about those relatives.

+ +

Iterating the Tree

+ +

If you’d like to iterate through all the nodes, use the iterator() method to obtain an iterator that will loop through each node in the tree (using pre-order traversal):

+ +
foreach ($document->iterator() as $node) {
+    echo 'Current node: ' . get_class($node) . "\n";
+}
+
+ +

Given an AST like this (XML representation):

+ +
<document>
+  <heading level="1">
+    <text>Hello World!</text>
+  </heading>
+  <paragraph>
+    <text>This is an example of </text>
+    <strong>
+      <text>CommonMark</text>
+    </strong>
+    <text>.</text>
+  </paragraph>
+</document>
+
+ +

The code above will output:

+ +
Current node: League\CommonMark\Node\Block\Document
+Current node: League\CommonMark\Extension\CommonMark\Node\Block\Heading
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Block\Paragraph
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Extension\CommonMark\Node\Inline\Strong
+Current node: League\CommonMark\Node\Inline\Text
+Current node: League\CommonMark\Node\Inline\Text
+
+ +

This iterator doesn’t use recursion, so you won’t blow the stack when working with deeply-nested nodes. It’s also very CPU and memory-efficient.

+ +

Be careful when modifying nodes while iterating the tree as some of those changes may affect the current iteration process, especially for sibling nodes that come after the current one. For example, if you remove the current node’s next() sibling, the next loop of that iteration will still include the removed sibling even though it was successfully removed from the AST. Similarly, any new siblings that are added won’t be visited on the next loop.

+ +

Walking the Tree

+ +

If you’d like to walk through all the nodes, visiting each one as you enter and leave it, use the walker() method to obtain an instance of NodeWalker. This also uses pre-order traversal but emitting NodeWalkerEvents along the way:

+ +
use League\CommonMark\Node\NodeWalker;
+
+/** @var NodeWalker $walker */
+$walker = $document->walker();
+while ($event = $walker->next()) {
+    echo 'Now ' . ($event->isEntering() ? 'entering' : 'leaving') . ' a ' . get_class($event->getNode()) . ' node' . "\n";
+}
+
+ +

Using the same example AST in the previous section, this code will output:

+ +
Now entering a League\CommonMark\Node\Block\Document node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Block\Heading node
+Now entering a League\CommonMark\Node\Block\Paragraph node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now entering a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Extension\CommonMark\Node\Inline\Strong node
+Now entering a League\CommonMark\Node\Inline\Text node
+Now leaving a League\CommonMark\Node\Block\Paragraph node
+Now leaving a League\CommonMark\Node\Block\Document node
+
+ +

This approach offers many of the same benefits as the simple iteration shown in the previous section such as memory efficiency and no recursion. The key differences come from how you enter and leave nodes:

+ +
    +
  1. Iteration can potentially take twice as long - not ideal for performance
  2. +
  3. Provides you with more control over exactly when an action is taken on a node which is sometimes needed for certain AST manipulations
  4. +
  5. Also provides a resumeAt() method to override where it should iterate next
  6. +
+ +

But like with the iterator, be careful when adding/removing nodes while walking the tree, as there are even more subtle cases where the walker could even lose track of where it was, which may result in some nodes being visited multiple times or not at all.

+ +

Querying Nodes

+ +

If you’re trying to locate certain nodes to perform actions on them, querying the nodes from the AST might be easier to implement. This can be done with the Query class:

+ +
use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Node\Block\Paragraph;
+use League\CommonMark\Node\Query;
+
+// Find all paragraphs and blockquotes that contain links
+$matchingNodes = (new Query())
+    ->where(Query::type(Paragraph::class))
+    ->orWhere(Query::type(BlockQuote::class))
+    ->andWhere(Query::hasChild(Query::type(Link::class)))
+    ->findAll($document);
+
+foreach ($matchingNodes as $node) {
+    // TODO: Do something with them
+}
+
+ +

Each condition passed into where(), orWhere(), or andWhere() must be a callable “filter” that accepts a Node and returns true or false. We provide several methods that can help create these filters for you:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodDescription
Query::type(string $class)Creates a filter that matches nodes with the given class name
Query::hasChild()Creates a filter that matches nodes which contain at least one child
Query::hasChild(callable $condition)Creates a filter that matches nodes which contain at least one child that matches the inner $condition
Query::hasParent()Creates a filter that matches nodes which have a parent
Query::hasParent(callable $condition)Creates a filter that matches nodes which have a parent that matches the inner $condition
+ +

You can of course create your own custom filters/conditions using an anonymous function or by implementing ExpressionInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Node\Query;
+use League\CommonMark\Node\Query\ExpressionInterface;
+
+class ChildCountGreaterThan implements ExpressionInterface
+{
+    private $count;
+
+    public function __construct(int $count)
+    {
+        $this->count = $count;
+    }
+
+    public function __invoke(Node $node) : bool{
+        return count($node->children()) > $this->count;
+    }
+}
+
+$query = (new Query())
+    ->where(function (Node $node): bool { return $node->data->has('attributes/class'); })
+    ->andWhere(new ChildCountGreaterThan(3));
+
+ +

Modification

+ +

The following methods can be used to modify the AST:

+ +
    +
  • insertAfter(Node $sibling)
  • +
  • insertBefore(Node $sibling)
  • +
  • replaceWith(Node $replacement)
  • +
  • detach()
  • +
  • appendChild(Node $child)
  • +
  • prependChild(Node $child)
  • +
  • detachChildren()
  • +
  • replaceChildren(Node[] $children)
  • +
+ +

DocumentParsedEvent

+ +

The best way to access and manipulate the AST is by adding an event listener for the DocumentParsedEvent.

+ +

Data Storage

+ +

Each Node has a property called data which is a Data (array-like) object. This can be used to store any arbitrary data you’d like on the node:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text1 = new Text('Hello, world!');
+$text1->data->set('language', 'English');
+$text1->data->set('is_good_translation', true);
+
+$text2 = new Text('Bonjour monde!');
+$text2->data->set('language', 'French');
+$text2->data->set('is_good_translation', false);
+
+foreach ([$text1, $text2] as $text) {
+    if ($text->data->get('is_good_translation')) {
+        sprintf('In %s we would say: "%s"', $text->data->get('language'), $text->getLiteral());
+    } else {
+        sprintf('I think they would say "%s" in %s, but I\'m not sure.', $text->getLiteral(), $text->data->get('language'));
+    }
+}
+
+ +

You can also access deeply-nested paths using / or . as delimiters:

+ +
use League\CommonMark\Node\Inline\Text;
+
+$text = new Text('Hello, world!');
+$text->data->set('info', ['language' => 'English', 'is_good_translation' => true]);
+
+var_dump($text->data->get('info/language'));
+var_dump($text->data->get('info.is_good_translation'));
+
+$text->data->set('info/is_example', true);
+
+ +

HTML Attributes

+ +

The data property comes pre-instantiated with a single data element called attributes which is used to store any HTML attributes that need to be rendered. For example:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+$link = new Link('https://twitter.com/colinodell', '@colinodell');
+$link->data->append('attributes/class', 'social-link');
+$link->data->append('attributes/class', 'twitter');
+$link->data->set('attributes/target', '_blank');
+$link->data->set('attributes/rel', 'noopener');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/block-parsing/index.html b/2.5/customization/block-parsing/index.html new file mode 100644 index 0000000000..ae95edab82 --- /dev/null +++ b/2.5/customization/block-parsing/index.html @@ -0,0 +1,552 @@ + + + + + + + + + + + + + + + + + Block Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Block Parsing

+ +

At a high level, block parsing is a two-step process:

+ +
    +
  1. Using a BlockStartParserInterface to identify if/where a block start exists on the given line
  2. +
  3. Using a BlockContinueParserInterface to perform additional processing of the identified block
  4. +
+ +

So to implement a custom block parser you will actually need to implement both of these classes.

+ +

BlockStartParserInterface

+ +

Instances of this interface have a single tryStart() method:

+ +
/**
+ * Check whether we should handle the block at the current position
+ *
+ * @param Cursor                       $cursor
+ * @param MarkdownParserStateInterface $parserState
+ *
+ * @return BlockStart|null
+ */
+public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart;
+
+ +

Given a Cursor at the current position, plus some extra information about the state of the parser, this method is responsible for determining whether a particular type of block seems to exist at the given position. You don’t actually parse the block here - that’s the job of a BlockContinueParserInterface. Your only job here is to return whether or not a particular type of block does exist here, and if so which block parser should parse it.

+ +

If you find that you cannot parse the given block, you should return BlockStart::none(); from this function.

+ +

However, if the Markdown at the current position does indeed seem to be the type of block you’re looking for, you should return a BlockStart instance using the following static constructor pattern:

+ +
use League\CommonMark\Parser\Block\BlockStart;
+
+return BlockStart::of(new MyCustomParser())->at($cursor);
+
+ +

Unlike in 1.x, the Cursor state is no longer shared between parsers. You must therefore explicitly provide the BlockStart object with a copy of your cursor at the correct, post-parsing position.

+ +

NOTE: If your custom block starts with a letter character you’ll need to add your parser to the environment with a priority of 250 or higher. This is due to a performance optimization where such lines are usually skipped.

+ +

BlockContinueParserInterface

+ +

The previous interface only helps the engine identify where a block starts. Additional information about the block, as well as the ability to parse additional lines of input, is all handled by the BlockContinueParserInterface.

+ +

This interface has several methods, so it’s usually easier to extend from AbstractBlockContinueParser instead, which sets most of the methods to use typical defaults you can override as needed.

+ +

getBlock()

+ +
public function getBlock(): AbstractBlock;
+
+ +

Each instance of a BlockContinueParserInterface is associated with a new block that is being parsed. This method here returns that block.

+ +

isContainer()

+ +
public function isContainer(): bool;
+
+ +

This method returns whether or not the block is a “container” capable of containing other blocks as children.

+ +

canContain()

+ +
public function canContain(AbstractBlock $childBlock): bool;
+
+ +

This method returns whether the current block being parsed can contain the given child block.

+ +

canHaveLazyContinuationLines()

+ +
public function canHaveLazyContinuationLines(): bool;
+
+ +

This method returns whether or not this parser should also receive subsequent lines of Markdown input. This is primarily used when a block can span multiple lines, like code blocks do.

+ +

addLine()

+ +
public function addLine(string $line): void;
+
+ +

If canHaveLazyContinuationLines() returned true, this method will be called with the additional lines of content.

+ +

tryContinue()

+ +
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue;
+
+ +

This method allows you to try and parse an additional line of Markdown.

+ +

closeBlock()

+ +
public function closeBlock(): void;
+
+ +

This method is called when the block is done being parsed. Any final adjustments to the block should be made at this time.

+ +

parseInlines()

+ +
public function parseInlines(InlineParserEngineInterface $inlineParser): void;
+
+ +

This method is called when the engine is ready to parse any inline child elements.

+ +

Note: For performance reasons, this method is not part of BlockContinueParserInterface. If your block may contain inlines, you should make sure that your “continue parser” also implements BlockContinueParserWithInlinesInterface.

+ +

Tips

+ +

Here are some additional tips to consider when writing your own custom parsers:

+ +

Combining both into one file

+ +

Although parsing requires two classes, you can use the anonymous class feature of PHP to combine both into a single file! Here’s an example:

+ +
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
+use League\CommonMark\Parser\Block\BlockStartParserInterface;
+
+final class MyCustomBlockParser extends AbstractBlockContinueParser
+{
+    // TODO: implement your continuation parsing methods here
+
+    public static function createBlockStartParser(): BlockStartParserInterface
+    {
+        return new class implements BlockStartParserInterface
+        {
+            // TODO: implement the tryStart() method here
+        };
+    }
+}
+
+
+ +

Performance

+ +

The BlockStartParserInterface::tryStart() and BlockContinueParserInterface::tryContinue() methods may be called hundreds or thousands of times during execution. For best performance, have your methods return as early as possible, and make sure your code is highly optimized.

+ +

Block Elements

+ +

In addition to creating a block parser, you may also want to have it return a custom “block element” - this is a class that extends from AbstractBlock and represents that particular block within the AST.

+ +

If your block contains literal strings/text within the block (and not as part of a child block), you should have your custom block type also implement StringContainerInterface.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/configuration/index.html b/2.5/customization/configuration/index.html new file mode 100644 index 0000000000..0b51b2703f --- /dev/null +++ b/2.5/customization/configuration/index.html @@ -0,0 +1,508 @@ + + + + + + + + + + + + + + + + + Configuration - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Configuration Schemas and Values

+ +

Version 2.0 introduced a new robust system for defining configuration schemas and accessing them within custom extensions.

+ +

Configuration Schemas

+ +

Unlike in 1.x, all configuration options must have a defined schema. This defines which options are available, what types of values they accept, whether any are required, and any default values you wish to define if the user doesn’t provide any.

+ +

These custom options can be defined from within your custom extension by implementing the ConfigurableExtensionInterface:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+use Nette\Schema\Expect;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        $builder->addSchema('my_extension', Expect::structure([
+            'enable_some_feature' => Expect::bool()->default(true),
+            'html_class' => Expect::string()->default('my-custom-extension'),
+            'align' => Expect::anyOf('left', 'center', 'right')->default('left'),
+            'favorite_number' => Expect::int()->min(1)->max(100)->default(42),
+        ]));
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        // TODO: Implement register() method
+    }
+}
+
+ +

See the league/config documentation for more examples of how to define custom configuration schemas.

+ +

Note that you only need to implement ConfigurableExtensionInterface if you plan to define new configuration options - you don’t need this if you’re only reading existing options.

+ +

Reading Configuration Values

+ +

Okay, so your extension has defined the different options that are available, but now you want to start using them within your custom extension. There are a few ways you can access the values:

+ +

During Extension Registration

+ +

Perhaps your extension needs to decide whether/how to register certain parsers/renderers/etc based on the user-provided configuration values - in that case, you can read the value from the $environment - for example:

+ +
use League\Config\ConfigurationBuilderInterface;
+use League\CommonMark\Environment\EnvironmentBuilderInterface;
+use League\CommonMark\Extension\ConfigurableExtensionInterface;
+
+final class MyCustomExtension implements ConfigurableExtensionInterface
+{
+    public function configureSchema(ConfigurationBuilderInterface $builder): void
+    {
+        // (see code example above)
+    }
+
+    public function register(EnvironmentBuilderInterface $environment): void
+    {
+        if ($environment->getConfiguration()->get('my_extension/enable_some_feature')) {
+            $environment->addBlockStartParser(new MyCustomParser());
+            $environment->addRenderer(MyCustomBlockType::class, new MyCustomRenderer());
+        }
+    }
+}
+
+ +

Within Parsers/Renderers/Listeners

+ +

Perhaps you want to reference those configuration values from within a custom parser, renderer, event listener, or something else. This can easily by done by having that class also implement ConfigurationAwareInterface. This interface signals to the Environment that your class needs a copy of the final configuration so it can read it later:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\Config\ConfigurationAwareInterface;
+use League\Config\ConfigurationInterface;
+
+final class MyCustomRenderer implements NodeRendererInterface, ConfigurationAwareInterface
+{
+    /**
+     * @var ConfigurationInterface
+     */
+    private $config;
+
+    public function setConfiguration(ConfigurationInterface $configuration): void
+    {
+        $this->config = $configuration;
+    }
+
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return 'My favorite number is ' . $this->config->get('my_extension/favorite_number');
+    }
+}
+
+ +

You can access any configuration value from here, not just the ones you might have defined yourself.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/cursor/index.html b/2.5/customization/cursor/index.html new file mode 100644 index 0000000000..3c2869a342 --- /dev/null +++ b/2.5/customization/cursor/index.html @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + Cursor - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Cursor

+ +

A Cursor is essentially a fancy string wrapper that remembers your current position as you parse it. It contains a set of highly-optimized methods making it easy to parse characters, match regular expressions, and more.

+ +

Supported Encodings

+ +

As of now, only UTF-8 (and, by extension, ASCII) encoding is supported.

+ +

Usage

+ +

Instantiating a new Cursor is as simple as:

+ +
use League\CommonMark\Parser\Cursor;
+
+$cursor = new Cursor('Hello World!');
+
+ +

Or, if you’re creating a custom block parser or inline parser, a pre-configured Cursor will be provided to you with (with the Cursor already set to the current position trying to be parsed).

+ +

Methods

+ +

You can then call any of the following methods to parse the string within that Cursor:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodPurpose
getPosition()Returns the current position/index of the Cursor within the string
getColumn()Returns the current column (used when handling tabbed indentation)
getIndent()Returns the current amount of indentation
isIndented()Returns whether the cursor is indented to INDENT_LEVEL
getCharacter(int $index)Returns the character at the given absolute position
getCurrentCharacter()Returns the character at the current position
peek()Returns the next character without changing the current position of the cursor
peek(int $offset)Returns the character $offset chars away without changing the current position of the cursor
getNextNonSpacePosition()Returns the position of the next character which is not a space or tab
getNextNonSpaceCharacter()Returns the next character which isn’t a space (or tab)
advance()Moves the cursor forward by 1 character
advanceBy(int $characters)Moves the cursor forward by $characters characters
advanceBy(int $characters, true)Moves the cursor forward by $characters characters, handling tabs as columns
advanceBySpaceOrTab()Advances forward one character (and returns true) if it’s a space or tab; returns false otherwise
advanceToNextNonSpaceOrTab()Advances forward past all spaces and tabs found, returning the number of such characters found
advanceToNextNonSpaceOrNewline()Advances forward past all spaces and newlines found, returning the number of such characters found
advanceToEnd()Advances the position to the very end of the string, returning the number of such characters passed
match(string $regex)Attempts to match the given $regex; returns null if matching fails, otherwise it advances past and returns the matched text
getPreviousText()Returns the text that was just advanced through during the last advance__() or match() operation
getRemainder()Returns the contents of the string from the current position through the end of the string
isBlank()Returns whether the remainder is blank (we’re at the end or only space characters remain)
isAtEnd()Returns whether the cursor has reached the end of the string
saveState()Encapsulates the current state of the cursor into an array in case you need to restoreState() later
restoreState($state)Pass the result of saveState() back into here to restore the original state of the Cursor
getLine()Returns the entire string (not taking the position into account)
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/delimiter-processing/index.html b/2.5/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..ecc9c5acd0 --- /dev/null +++ b/2.5/customization/delimiter-processing/index.html @@ -0,0 +1,504 @@ + + + + + + + + + + + + + + + + + Delimiter Processing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Delimiter Processing

+ +

Delimiter processors allow you to implement delimiter runs the same way the core library implements emphasis.

+ +

Delimiter runs are a special type of inline:

+ +
    +
  • They are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • They can contain other delimiter runs or inlines inside of them
  • +
+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

When implementing something with these characteristics you should consider leveraging delimiter runs; otherwise, a basic inline parser should be sufficient.

+ +

Delimiter Priority

+ +

Delimiter processors have a lower priority than inline parsers - if an inline parser successfully handles the same special character you’re interested in then your delimiter processor will not be called.

+ +

Implementing Standalone Delimiter Processors

+ +

Implement the DelimiterProcessorInterface and add it to your environment:

+ +
$environment->addDelimiterProcessor(new MyCustomDelimiterProcessor());
+
+ +

getOpeningCharacter() and getClosingCharacter()

+ +

These two methods tell the engine which characters are used to delineate your custom syntax. Generally these will be the same, such as when using *emphasis*, but they can be different; for example, maybe you want to use {this syntax}. Simply tell the engine which characters you’d like to use.

+ +

getMinimumLength()

+ +

This method tells the engine the minimum number of characters needed to match or “activate” your processor. For example, if you want to match {{example}} and not {example}, set this to 2.

+ +

getDelimiterUse()

+ +
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
+
+ +

This method is used to tell the engine how many characters from the matching delimiters should be consumed. For simple processors you’ll likely return 1 (or whatever your minimum length is). In more advanced cases, you can examine the opening and closing delimiters and perform additional logic to determine whether they should be fully or partially consumed. You can also return 0 if you’d like.

+ +

process()

+ +
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void;
+
+ +

This is where the magic happens. Once the engine determines it can use the delimiter it found (by looking at all the other methods above) it’ll call this method. Your job is to take everything between the $opener and $closer and wrap that in whatever custom inline element you’d like. Here’s a basic example of wrapping the inner contents inside a new Emphasis element:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
+
+// Create the outer element
+$emphasis = new Emphasis();
+
+// Add everything between $opener and $closer (exclusive) to the new outer element
+$tmp = $opener->next();
+while ($tmp !== null && $tmp !== $closer) {
+    $next = $tmp->next();
+    $emphasis->appendChild($tmp);
+    $tmp = $next;
+}
+
+// Place the outer element into the AST
+$opener->insertAfter($emphasis);
+
+ +

Note that $opener and $closer will be automatically removed for you after this function returns - no need to do that yourself.

+ +

Combining Inline Parsers with Delimiter Processors

+ +

Basic delimiter processors, as covered above, do not require any custom inline parsers - they’ll “just work”. But in some rare cases you may want to pair it with a custom inline parser: the inline parser will identify the delimiter, adding an entry to the delimiter stack for the processor to process later. Note that this is an advanced use case and you probably don’t need this. But if you do then read on.

+ +

Inline Parsers and the Delimiter Stack

+ +

As your identifies potential delimiter-based inlines, it should create a new AbstractStringContainer node (either Text or something custom) with the inner contents and also push a new DelimiterInterface onto the DelimiterStack:

+ +
use League\CommonMark\Delimiter\Delimiter;
+use League\CommonMark\Node\Inline\Text;
+
+$node = new Text($cursor->getPreviousText(), [
+    'delim' => true,
+]);
+$inlineContext->getContainer()->appendChild($node);
+
+// Add entry to stack to this opener
+$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
+$inlineContext->getDelimiterStack()->push($delimiter);
+
+ +

This basically tells the engine that text was found which might be emphasis, but due to the delimiter run rules we can’t make that determination just yet. That final determination is later on by a “delimiter processor”.

+ +

Your implementation of the delimiter processor won’t look any different in this approach - you’ll still need to implement all of the same methods especially process(). The difference is that you’ve identified where the delimiter is, instead of relying on the engine to do this for you.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/disabling-features/index.html b/2.5/customization/disabling-features/index.html new file mode 100644 index 0000000000..d43fbab1b6 --- /dev/null +++ b/2.5/customization/disabling-features/index.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + Disabling Features - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Disabling Features

+ +

The CommonMark parser is designed to be highly configurable. You can disable certain features that you don’t want to have in your application. There are a few ways to do this, depending on your needs:

+ +

Avoiding Parsing

+ +

You cannot disable an already-registered parser, but you can prevent it from being registered with +the Environment in the first place. This is exactly how the +InlinesOnlyExtension works - it’s a copy of the CommonMarkCoreExtension class but +with the parsers we don’t want removed.

+ +

You can mirror this approach by defining your own custom extension class that registers +only the specific parsers, renderers, etc. that you want.

+ +

The only potential downside to this approach is that any syntax for those disabled features will appear in the output. +For example, if you were to prevent block quotes from being parsed, then the following Markdown:

+ +
> This is a block quote
+
+ +

Would have the > character appear in the output HTML:

+ +
<p>&gt; This is a block quote</p>
+
+ +

This is probably fine for most use cases.

+ +

Removing Parsed Elements

+ +

An alternative approach is to keep the parser enabled, but remove the parsed elements from the AST before rendering.

+ +

You’d create an event listener +(sort of like this one) that will +iterate all parsed elements, locate the target nodes, and remove them +by calling $node->detach().

+ +

There are three potential advantages to this approach:

+ +
    +
  1. You don’t need to create a custom extension class or prevent parsers from being registered
  2. +
  3. You can selectively remove certain elements based on their properties (e.g. only remove heading levels 3-6) while keeping others
  4. +
  5. The syntax and contents of the removed elements will not appear in the output HTML
  6. +
+ +

The downside is that you still incur the overhead of parsing the elements that are eventually removed.

+ +

Override Rendering

+ +

The final approach is to keep the parser enabled, but override how the parsed elements are rendered. For example, +you could implement a custom renderer for certain elements that simply returns +something else (perhaps an empty string, or an HTML comment of <!-- REMOVED -->) instead of the HTML you don’t want.

+ +

This approach is not recommended because:

+ +
    +
  1. You still incur the overhead of parsing the elements that are eventually removed
  2. +
  3. You’d need to register your custom renderer with a higher priority than the default renderer
  4. +
  5. You’d need to repeat this for every renderer that could potentially render the elements you want to remove
  6. +
+ +

It should technically work though, if you really want to go this route.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/environment/index.html b/2.5/customization/environment/index.html new file mode 100644 index 0000000000..af9046a602 --- /dev/null +++ b/2.5/customization/environment/index.html @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + The Environment - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

The Environment

+ +

The Environment contains all of the parsers, renderers, configurations, etc. that the library uses during the conversion process. You therefore must register all extensions, parsers, renderers, etc. with the Environment so that the library is aware of them.

+ +

An empty Environment can be obtained like this:

+ +
use League\CommonMark\Environment\Environment;
+
+$config = [];
+$environment = new Environment($config);
+
+ +

You can customize the Environment using any of the methods below (from the EnvironmentBuilderInterface interface).

+ +

Once your Environment is configured with whatever configuration and extensions you want, you can instantiate a MarkdownConverter and start converting MD to HTML:

+ +
use League\CommonMark\MarkdownConverter;
+
+// Using $environment from the previous code sample
+$converter = new MarkdownConverter($environment);
+
+echo $converter->convert('# Hello World!');
+
+ +

addExtension()

+ +
public function addExtension(ExtensionInterface $extension);
+
+ +

Registers the given extension with the environment. For example, if you want core CommonMark functionality plus footnote support:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+
+$config = [];
+$environment = new Environment($config);
+
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new FootnoteExtension());
+
+ +

addBlockStartParser()

+ +
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0);
+
+ +

Registers the given BlockStartParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Block Parsing for details.

+ +

addInlineParser()

+ +
public function addInlineParser(InlineParserInterface $parser, int $priority = 0);
+
+ +

Registers the given InlineParserInterface with the environment with the given priority (a higher number will be executed earlier).

+ +

See Inline Parsing for details.

+ +

addDelimiterProcessor()

+ +
public function addDelimiterProcessor(DelimiterProcessorInterface $processor);
+
+ +

Registers the given DelimiterProcessorInterface with the environment.

+ +

See Inline Parsing for details.

+ +

addRenderer()

+ +
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0);
+
+ +

Registers a NodeRendererInterface to handle a specific type of AST node ($nodeClass) with the given priority (a higher number will be executed earlier).

+ +

See Rendering for details.

+ +

addEventListener()

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0);
+
+ +

Registers the given event listener with the environment.

+ +

See Event Dispatcher for details.

+ +

Priority

+ +

Several of these methods allows you to specify a numeric $priority. In cases where multiple things are registered, the internal engine will attempt to use the higher-priority ones first, falling back to lower priority ones if the first one(s) were unable to handle things.

+ +

Accessing the Environment and Configuration within parsers/renderers/etc

+ +

If your custom parser/renderer/listener/etc. implements either EnvironmentAwareInterface or ConfigurationAwareInterface we’ll automatically inject the environment or configuration into them once the environment has been fully initialized. This will provide your code with access to the finalized information it may need.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/event-dispatcher/index.html b/2.5/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..6fe21ae1e6 --- /dev/null +++ b/2.5/customization/event-dispatcher/index.html @@ -0,0 +1,580 @@ + + + + + + + + + + + + + + + + + Event Dispatcher - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Event Dispatcher

+ +

This library includes basic, PSR-14-compliant event dispatcher functionality. This makes it possible to add hook points throughout the library and third-party extensions which other code can listen for and execute code.

+ +

Event Class

+ +

Any PSR-14 compliant event can be used, though we also provide an AbstractEvent class you can use to easily create your own events:

+ +
use League\CommonMark\Event\AbstractEvent;
+
+class MyCustomEvent extends AbstractEvent {}
+
+ +

An event can have any number of methods on it which return useful information the listeners can use or modify.

+ +

Registering Listeners

+ +

Listeners can be registered with the Environment using the addEventListener() method:

+ +
public function addEventListener(string $eventClass, callable $listener, int $priority = 0)
+
+ +

The parameters for this method are:

+ +
    +
  1. The fully-qualified name of the event class you wish to observe
  2. +
  3. Any PHP callable to execute when that type of event is dispatched
  4. +
  5. An optional priority (defaults to 0)
  6. +
+ +

For example:

+ +
// Telling the environment which method to call:
+$customListener = new MyCustomListener();
+$environment->addEventListener(MyCustomEvent::class, [$customListener, 'onDocumentParsed']);
+
+// Or if MyCustomerListener has an __invoke() method:
+$environment->addEventListener(MyCustomEvent::class, new MyCustomListener(), 10);
+
+// Or use any other type of callable you wish!
+$environment->addEventListener(MyCustomEvent::class, function (MyCustomEvent $event) {
+    // TODO: Stuff
+}, 10);
+
+ +

Dispatching Events

+ +

Events can be dispatched via the $environment->dispatch() method which takes a single argument - the event object to dispatch:

+ +
$environment->dispatch(new MyCustomEvent());
+
+ +

Listeners will be called in order of priority (higher priorities will be called first). If multiple listeners have the same priority, they’ll be called in the order in which they were registered. If you’d like your listener to prevent other subsequent events from running, simply call $event->stopPropagation().

+ +

Listeners may call any method on the event to get more information about the event, make changes to event data, etc.

+ +

List of Available Events

+ +

This library supports the following default events which you can register listeners for:

+ +

League\CommonMark\Event\DocumentPreParsedEvent

+ +

This event is dispatched just before any processing is done. It can be used to pre-populate reference map of a document or manipulate the Markdown contents before any processing is performed.

+ +

League\CommonMark\Event\DocumentParsedEvent

+ +

This event is dispatched once all other processing is done. This offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering.

+ +

League\CommonMark\Event\DocumentPreRenderEvent

+ +

This event is dispatched by the renderer just before rendering begins. Like with DocumentParsedEvent, this offers extensions the opportunity to inspect and modify the Abstract Syntax Tree prior to rendering, but with the added knowledge of which format is being rendered to (e.g. html).

+ +

League\CommonMark\Event\DocumentRenderedEvent

+ +

This event is dispatched once the rendering step has been completed, just before the output is returned. The final output can be adjusted at this point or additional metadata can be attached to the return object.

+ +

Bring Your Own PSR-14 Event Dispatcher

+ +

Although this library provides PSR-14 compliant event dispatching out-of-the-box, you may want to use your own PSR-14 event dispatcher instead. This is possible as long as that third-party library both:

+ +
    +
  1. Implements the PSR-14 EventDispatcherInterface; and,
  2. +
  3. Allows you to register additional ListenerProviderInterface instances with that dispatcher library
  4. +
+ +

Not all libraries support this so please check carefully! Assuming yours does, delegating all the event behavior to that library can be done with two steps:

+ +

First, call the setEventDispatcher() method on the Environment to register that other implementation. With that done, any calls to Environment::dispatch() will be passed through to that other dispatcher. But we still need to let that dispatcher know about the events registered by CommonMark extensions, otherwise nothing will happen when events are dispatched.

+ +

Because the Environment implements PSR-14’s ListenerProviderInterface you’ll also need to pass the configured Environment object to your event dispatcher so that it becomes aware of those available events.

+ +

Example

+ +

Here’s an example of a listener which uses the DocumentParsedEvent to add an external-link class to external URLs:

+ +
use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Event\DocumentParsedEvent;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+
+class ExternalLinkProcessor
+{
+    private $environment;
+
+    public function __construct(EnvironmentInterface $environment)
+    {
+        $this->environment = $environment;
+    }
+
+    public function onDocumentParsed(DocumentParsedEvent $event): void
+    {
+        $document = $event->getDocument();
+        $walker = $document->walker();
+        while ($event = $walker->next()) {
+            $node = $event->getNode();
+
+            // Only stop at Link nodes when we first encounter them
+            if (!($node instanceof Link) || !$event->isEntering()) {
+                continue;
+            }
+
+            $url = $node->getUrl();
+            if ($this->isUrlExternal($url)) {
+                $node->data->append('attributes/class', 'external-link');
+            }
+        }
+    }
+
+    private function isUrlExternal(string $url): bool
+    {
+        // Only look at http and https URLs
+        if (!preg_match('/^https?:\/\//', $url)) {
+            return false;
+        }
+
+        $host = parse_url($url, PHP_URL_HOST);
+
+        return $host != $this->environment->getConfiguration()->get('host');
+    }
+}
+
+ +

And here’s how you’d use it:

+ +
use League\CommonMark\CommonMarkConverter;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Event\DocumentParsedEvent;
+
+$env = new Environment();
+
+$listener = new ExternalLinkProcessor($env);
+$env->addEventListener(DocumentParsedEvent::class, [$listener, 'onDocumentParsed']);
+
+$converter = new CommonMarkConverter(['host' => 'commonmark.thephpleague.com'], $env);
+
+$input = 'My two favorite sites are <https://google.com> and <https://commonmark.thephpleague.com>';
+
+echo $converter->convert($input);
+
+ +

Output (formatted for readability):

+ +
<p>
+    My two favorite sites are
+    <a class="external-link" href="https://google.com">https://google.com</a>
+    and
+    <a href="https://commonmark.thephpleague.com">https://commonmark.thephpleague.com</a>
+</p>
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/extensions/index.html b/2.5/customization/extensions/index.html new file mode 100644 index 0000000000..0bb237b5db --- /dev/null +++ b/2.5/customization/extensions/index.html @@ -0,0 +1,447 @@ + + + + + + + + + + + + + + + + + Extensions - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Extensions

+ +

Extensions provide a way to group related parsers, renderers, etc. together with pre-defined priorities, configuration settings, etc. They are perfect for distributing your customizations as reusable, open-source packages that others can plug into their own projects!

+ +

To create an extension, simply create a new class implementing ExtensionInterface. This has a single method where you’re given a ConfigurableEnvironmentInterface to register whatever things you need to. For example:

+ +
use League\CommonMark\Extension\ExtensionInterface;
+use League\CommonMark\Environment\ConfigurableEnvironmentInterface;
+
+final class EmojiExtension implements ExtensionInterface
+{
+    public function register(ConfigurableEnvironmentInterface $environment): void
+    {
+        $environment
+            // TODO: Create the EmojiParser, Emoji, and EmojiRenderer classes
+            ->addInlineParser(new EmojiParser(), 20)
+            ->addInlineRenderer(Emoji::class, new EmojiRenderer(), 0)
+        ;
+    }
+}
+
+ +

To hook up your new extension to the Environment, simply do this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new EmojiExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello! :wave:');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/inline-parsing/index.html b/2.5/customization/inline-parsing/index.html new file mode 100644 index 0000000000..713c076c0e --- /dev/null +++ b/2.5/customization/inline-parsing/index.html @@ -0,0 +1,598 @@ + + + + + + + + + + + + + + + + + Inline Parsing - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Inline Parsing

+ +

There are two ways to implement custom inline syntax:

+ + + +

The difference between normal inlines and delimiter-run-based inlines is subtle but important to understand. In a nutshell, delimiter-run-based inlines:

+ +
    +
  • Are denoted by “wrapping” text with one or more characters before and after those inner contents
  • +
  • Can contain other delimiter runs or inlines inside of them
  • +
+ +

An example of this would be emphasis:

+ +
This is an example of **emphasis**. Note how the text is *wrapped* with the same character(s) before and after.
+
+ +

If your syntax looks like that, consider using a delimiter processor instead. Otherwise, an inline parser is your best bet.

+ +

Implementing Inline Parsers

+ +

Inline parsers should implement InlineParserInterface and the following two methods:

+ +

getMatchDefinition()

+ +

This method should return an instance of InlineParserMatch which defines the text the parser is looking for. Examples of this might be something like:

+ +
use League\CommonMark\Parser\Inline\InlineParserMatch;
+
+InlineParserMatch::string('@');                  // Match any '@' characters found in the text
+InlineParserMatch::string('foo');                // Match the text 'foo' (case insensitive)
+
+InlineParserMatch::oneOf('@', '!');              // Match either character
+InlineParserMatch::oneOf('http://', 'https://'); // Match either string
+
+InlineParserMatch::regex('\d+');                 // Match the regular expression (omit the regex delimiters and any flags)
+
+ +

Once a match is found, the parse() method below may be called.

+ +

parse()

+ +

This method will be called if both conditions are met:

+ +
    +
  1. The engine has found at a matching string in the current line; and,
  2. +
  3. No other inline parsers with a higher priority have successfully parsed the text at this point in the line
  4. +
+ +

Parameters

+ +
    +
  • InlineParserContext $inlineContext - Encapsulates the current state of the inline parser - see more information below.
  • +
+ +
InlineParserContext
+ +

This class has several useful methods:

+ +
    +
  • getContainer() - Returns the current container block the inline text was found in. You’ll almost always call $inlineContext->getContainer()->appendChild(...) to add the parsed inline text inside that block.
  • +
  • getReferenceMap() - Returns the document’s reference map
  • +
  • getCursor() - Returns the current Cursor used to parse the current line. (Note that the cursor will be positioned before the matched text, so you must advance it yourself if you determine it’s a valid match)
  • +
  • getDelimiterStack() - Returns the current delimiter stack. Only used in advanced use cases.
  • +
  • getFullMatch() - Returns the full string that matched you InlineParserMatch definition
  • +
  • getFullMatchLength() - Returns the length of the full match - useful for advancing the cursor
  • +
  • getSubMatches() - If your InlineParserMatch used a regular expression with capture groups, this will return the text matches by those groups.
  • +
  • getMatches() - Returns an array where index 0 is the “full match”, plus any sub-matches. It basically simulates preg_match()’s behavior.
  • +
+ +

Return value

+ +

parse() should return false if it’s unable to handle the text at the current position for any reason. Other parsers will then have a chance to try parsing that text. If all registered parsers return false, the text will be added as plain text.

+ +

Returning true tells the engine that you’ve successfully parsed the character (and related ones after it). It is your responsibility to:

+ +
    +
  1. Advance the cursor to the end of the parsed/matched text
  2. +
  3. Add the parsed inline to the container ($inlineContext->getContainer()->appendChild(...))
  4. +
+ +

Inline Parser Examples

+ +

Example 1 - Twitter Handles

+ +

Let’s say you wanted to autolink Twitter handles without using the link syntax. This could be accomplished by registering a new inline parser to handle the @ character:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class TwitterHandleParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::regex('@([A-Za-z0-9_]{1,15}(?!\w))');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+        // The @ symbol must not have any other characters immediately prior
+        $previousChar = $cursor->peek(-1);
+        if ($previousChar !== null && $previousChar !== ' ') {
+            // peek() doesn't modify the cursor, so no need to restore state first
+            return false;
+        }
+
+        // This seems to be a valid match
+        // Advance the cursor to the end of the match
+        $cursor->advanceBy($inlineContext->getFullMatchLength());
+
+        // Grab the Twitter handle
+        [$handle] = $inlineContext->getSubMatches();
+        $profileUrl = 'https://twitter.com/' . $handle;
+        $inlineContext->getContainer()->appendChild(new Link($profileUrl, '@' . $handle));
+        return true;
+    }
+}
+
+// And here's how to hook it up:
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new TwitterHandleParser());
+
+ +

Example 2 - Emoticons

+ +

Let’s say you want to automatically convert smilies (or “frownies”) to emoticon images. This is incredibly easy with an inline parser:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
+use League\CommonMark\Parser\Inline\InlineParserInterface;
+use League\CommonMark\Parser\Inline\InlineParserMatch;
+use League\CommonMark\Parser\InlineParserContext;
+
+class SmilieParser implements InlineParserInterface
+{
+    public function getMatchDefinition(): InlineParserMatch
+    {
+        return InlineParserMatch::oneOf(':)', ':(');
+    }
+
+    public function parse(InlineParserContext $inlineContext): bool
+    {
+        $cursor = $inlineContext->getCursor();
+
+        // Advance the cursor past the 2 matched chars since we're able to parse them successfully
+        $cursor->advanceBy(2);
+
+        // Add the corresponding image
+        if ($inlineContext->getFullMatch() === ':)') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/happy.png'));
+        } elseif ($inlineContext->getFullMatch() === ':(') {
+            $inlineContext->getContainer()->appendChild(new Image('/img/sad.png'));
+        }
+
+        return true;
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addInlineParser(new SmilieParserParser());
+
+ +

Tips

+ +
    +
  • For best performance: +
      +
    • Avoid using overly-complex regular expressions in getMatchDefinition() - use the simplest regex you can and have parse() do the heavier validation
    • +
    • Have your parse() method return false as soon as possible.
    • +
    +
  • +
  • You can peek() without modifying the cursor state. This makes it useful for validating nearby characters as it’s quick and you can bail without needed to restore state.
  • +
  • You can look at (and modify) any part of the AST if needed (via $inlineContext->getContainer()).
  • +
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/overview/index.html b/2.5/customization/overview/index.html new file mode 100644 index 0000000000..3653151522 --- /dev/null +++ b/2.5/customization/overview/index.html @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + Customization Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Customization Overview

+ +

Ready to go beyond the basics of converting Markdown to HTML? This page describes some of the more advanced things you can customize this library to do.

+ +

Parsing and Rendering

+ +

The actual process of converting Markdown to HTML has several steps:

+ +
    +
  1. Create an Environment, adding whichever extensions/parser/renders/configuration you need
  2. +
  3. Instantiate a MarkdownParser and HtmlRenderer using that Environment
  4. +
  5. Use the MarkdownParser to parse the Markdown input into an Abstract Syntax Tree (aka an “AST”)
  6. +
  7. Use the HtmlRenderer to convert the AST Document into HTML
  8. +
+ +

The MarkdownConverter class handles all of this for you, but you can execute that process yourself if you wish:

+ +
use League\CommonMark\Parser\MarkdownParser;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Renderer\HtmlRenderer;
+
+$environment = new Environment([
+    'html_input' => 'strip',
+]);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$parser = new MarkdownParser($environment);
+$htmlRenderer = new HtmlRenderer($environment);
+
+$markdown = '# Hello World!';
+
+$document = $parser->parse($markdown);
+echo $htmlRenderer->renderDocument($document);
+
+// <h1>Hello World!</h1>
+
+ +

Feel free to swap out different components or add your own steps in between. However, the best way to customize this library is to create your own extensions which hook into the parsing and rendering steps - continue reading to see which kinds of extension points are available to you.

+ +

Add Custom Syntax with Parsers

+ +

Parsers examine the Markdown input and produce an abstract syntax tree (AST) of the document’s structure. +This resulting AST contains both blocks (structural elements like paragraphs, lists, headers, etc) and inlines (words, spaces, links, emphasis, etc).

+ +

There are two main types of parsers:

+ + + +

The parsing approach is identical for both types - examine text at the current position (via the Cursor) and determine if you can handle it; +if so, create the corresponding AST element, +otherwise you abort and the engine will try other parsers. If no parser succeeds then the current text is treated as plain text.

+ +

Simple delimiter-based inlines (like emphasis, strikethrough, etc.) can be parsed without needing a dedicated inline parser by leveraging the new Delimiter Processing functionality.

+ +

AST manipulation

+ +

Once the Abstract Syntax Tree is parsed, you are free to access/manipulate it as needed before it’s passed into the rendering engine.

+ +

Customize HTML Output with Custom Renderers

+ +

Renderers convert the parsed blocks/inlines from the AST representation into HTML. When registering these with the environment, you must tell it which block/inline classes it should handle. This allows you to essentially “swap out” built-in renderers with your own.

+ +

Examples

+ +

Some examples of what’s possible:

+ + + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/rendering/index.html b/2.5/customization/rendering/index.html new file mode 100644 index 0000000000..4f1672a047 --- /dev/null +++ b/2.5/customization/rendering/index.html @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Custom Rendering

+ +

Renderers are responsible for converting the parsed AST elements into their HTML representation.

+ +

All block renderers should implement NodeRendererInterface and its render() method. Note that in v2.0, both +block renderers and inline renderers share the same interface and method:

+ +

render()

+ +
public function render(Node $node, ChildNodeRendererInterface $childRenderer);
+
+ +

The HtmlRenderer will call this method during the rendering process whenever a supported element is encountered.

+ +

If your renderer can only handle certain block types, be sure to verify that you’ve been passed the correct type.

+ +

Parameters

+ +
    +
  • Node $node - The encountered block or inline element that needs to be rendered
  • +
  • ChildNodeRendererInterface $childRenderer - If the given $node has children, use this to render those child elements
  • +
+ +

Return value

+ +

The method must return the final HTML representation of the node and its contents, including any children. This can be an HtmlElement object (preferred; castable to a string), a string of raw HTML, or null if it could not render (and perhaps another renderer should give it a try).

+ +

If you choose to return an HTML string you are responsible for handling any escaping that may be necessary.

+ +

HtmlElement

+ +

Instead of manually building the HTML output yourself, you can leverage the HtmlElement to generate that for you. For example:

+ +
use League\CommonMark\Util\HtmlElement;
+
+$link = new HtmlElement('a', ['href' => 'https://github.com'], 'GitHub');
+$img = new HtmlElement('img', ['src' => 'logo.jpg'], '', true);
+
+ +

Designating Renderers

+ +

When registering your renderer, you must tell the Environment which node element class your renderer should handle. For example:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// First param - the node class type that should use our renderer
+// Second param - instance of the renderer
+$environment->addRenderer(FencedCode::class, new MyCustomCodeRenderer());
+
+ +

A single renderer could even be used for multiple types:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
+use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$myRenderer = new MyCustomCodeRenderer();
+
+$environment->addRenderer(FencedCode::class, $myRenderer, 10);
+$environment->addRenderer(IndentedCode::class, $myRenderer, 20);
+
+ +

Multiple renderers can be added per element type - when this happens, we use the result from the highest-priority renderer that returns a non-null result.

+ +

Example

+ +

Here’s a custom renderer which renders thematic breaks as text (instead of <hr>):

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak;
+use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class TextDividerRenderer implements NodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+}
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(ThematicBreak::class, new TextDividerRenderer());
+
+ +

Note that thematic breaks should not contain children, which is why the $childRenderer is unused in this example. Otherwise we’d have to call code like this and return the result as part of the rendered HTML we’re generating here: $innerHtml = $childRenderer->renderNodes($node->children());

+ +

Tips

+ +
    +
  • Return an HtmlElement if possible. This makes it easier to extend and modify the results later.
  • +
  • Don’t forget to render any child elements that your node might contain!
  • +
+ +

Wrapping Elements with HtmlDecorator

+ +

A utility class called HtmlDecorator is provided to make it easier to wrap the output of any renderer within an additional HTML tag with custom attributes and/or classes. To use it:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Renderer\HtmlDecorator;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\Extension\Table\TableRenderer;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addRenderer(Table::class, new HtmlDecorator(new TableRenderer(), 'div', ['class' => 'table-responsive']));
+
+ +

XML Rendering

+ +

The XML renderer will automatically attempt to convert any AST nodes to XML by inspecting the name of the block/inline node and its attributes. You can instead control the XML element name and attributes by making your renderer implement XmlNodeRendererInterface:

+ +
use League\CommonMark\Node\Node;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+use League\CommonMark\Xml\XmlNodeRendererInterface;
+
+class TextDividerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
+{
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        return new HtmlElement('pre', ['class' => 'divider'], '==============================');
+    }
+
+    public function getXmlTagName(Node $node): string
+    {
+        return 'text_divider';
+    }
+
+    public function getXmlAttributes(Node $node): array
+    {
+        return ['character' => '='];
+    }
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/customization/slug-normalizer/index.html b/2.5/customization/slug-normalizer/index.html new file mode 100644 index 0000000000..9035caa30f --- /dev/null +++ b/2.5/customization/slug-normalizer/index.html @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + Slug Normalizer - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Slug Normalizer

+ +

“Slugs” are strings used within href, name, and id HTML attributes to identify particular elements within a document.

+ +

Some extensions (like the HeadingPermalinkExtension) need the ability to convert user-provided text into these URL-safe slugs while also ensuring that these are unique throughout the generated HTML. The Environment provides a pre-built normalizer you can use for this purpose.

+ +

Usage

+ +

You can obtain a reference to the built-in slug normalizer by calling $environment->getSlugNormalizer();

+ +

To use this within your extension, have your parser/renderer/whatever implement EnvironmentAwareInterface and then implement the corresponding setEnvironment method like this:

+ +

+use League\CommonMark\Environment\EnvironmentInterface;
+use League\CommonMark\Environment\EnvironmentAwareInterface;
+
+class MyCustomParserOrRenderer implements EnvironmentAwareInterface
+{
+    private $slugNormalizer;
+
+    public function setEnvironment(EnvironmentInterface $environment): void
+    {
+        $this->slugNormalizer = $environment->getSlugNormalizer();
+    }
+}
+
+ +

You can then call $this->slugNormalizer->normalize($text) as needed.

+ +

Configuration

+ +

The slug_normalizer configuration section allows you to adjust the following options:

+ +

instance

+ +

You can change the string that is used as the “slug” by setting the instance option to any class that implements TextNormalizerInterface. +We provide a simple SlugNormalizer by default, but you may want to plug in a different library or create your own normalizer instead.

+ +

For example, if you’d like each slug to be an MD5 hash, you could create a class like this:

+ +
use League\CommonMark\Normalizer\TextNormalizerInterface;
+
+final class MD5Normalizer implements TextNormalizerInterface
+{
+    public function normalize(string $text, $context = null): string
+    {
+        return md5($text);
+    }
+}
+
+ +

And then configure it like this:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new MD5Normalizer(),
+    ],
+];
+
+ +

Or you could use PHP’s anonymous class feature to define the generator’s behavior without creating a new class file:

+ +
$config = [
+    'slug_normalizer' => [
+        // ... other options here ...
+        'instance' => new class implements TextNormalizerInterface {
+            public function normalize(string $text, $context = null): string
+            {
+                // TODO: Implement your code here
+            }
+        },
+    ],
+];
+
+ +

max_length

+ +

This can be configured to limit the length of that slug to prevent overly-long values. By default, that limit is 255 characters. You may set this to any positive integer, or 0 for no limit.

+ +

(Note that generated slugs might be slightly longer than this “limit” if the unique option is enabled and the slug generator detects a duplicate slug and needs to add a suffix to make it unique.)

+ +

unique

+ +

This options controls whether slugs should be unique. Possible values include:

+ +
    +
  • 'document' (string; default) - Ensures slugs are unique within a single document
  • +
  • 'environment' (string) - Ensures slugs are unique across multiple documents - see below
  • +
  • false (boolean) - Disables unique slug generation
  • +
+ +

You might have a use case where you’re converting several different Markdown documents on the same page and so you’d like to ensure that none of those documents use conflicting slugs. In that case, you should set the scope option to 'environment' to ensure that a single instance of a MarkdownConverter (which uses a single Environment) will never produce the same slug twice during its lifetime (which usually lasts the entire duration of a single HTTP request).

+ +

If you need complete control over how unique slugs are generated, make your 'instance' implement UniqueSlugNormalizerInterface; otherwise, we’ll simply append incremental numbers to slugs to ensure they are unique.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/attributes/index.html b/2.5/extensions/attributes/index.html new file mode 100644 index 0000000000..3bff8103e7 --- /dev/null +++ b/2.5/extensions/attributes/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Attributes

+ +

The AttributesExtension allows HTML attributes to be added from within the document.

+ +

Attribute Syntax

+ +

The basic syntax was inspired by Kramdown’s Attribute Lists feature.

+ +

You can assign any attribute to a block-level element. Just directly prepend or follow the block with a block inline attribute list. +That consists of a left curly brace, optionally followed by a colon, the attribute definitions and a right curly brace:

+ +
> A nice blockquote
+{: title="Blockquote title"}
+
+ +

This results in the following output:

+ +
<blockquote title="Blockquote title">
+<p>A nice blockquote</p>
+</blockquote>
+
+ +

CSS-selector-style declarations can be used to set the id and class attributes:

+ +
{#id .class}
+## Header
+
+ +

Output:

+ +
<h2 class="class" id="id">Header</h2>
+
+ +

As with a block-level element you can assign any attribute to a span-level elements using a span inline attribute list, +that has the same syntax and must immediately follow the span-level element:

+ +
This is *red*{style="color: red"}.
+
+ +

Output:

+ +
<p>This is <em style="color: red">red</em>.</p>
+
+ +

Empty-Value Attributes

+ +

Attributes can be rendered in HTML without a value by using true value in the markdown document:

+ +
{itemscope=true}
+## Header
+
+ +

Output:

+ +
<h2 itemscope>Header</h2>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Attributes\AttributesExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/autolinks/index.html b/2.5/extensions/autolinks/index.html new file mode 100644 index 0000000000..5d74f2894a --- /dev/null +++ b/2.5/extensions/autolinks/index.html @@ -0,0 +1,472 @@ + + + + + + + + + + + + + + + + + Autolink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Autolink Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The AutolinkExtension adds GFM-style autolinking. It automatically links URLs and email addresses even when the CommonMark <...> autolink syntax is not used.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the AutolinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'autolink' => [
+        'allowed_protocols' => ['https'], // defaults to ['https', 'http', 'ftp']
+        'default_protocol' => 'https', // defaults to 'http'
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new AutolinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!');
+
+ +

Configuration

+ +

As of version 2.5.0, this extension supports the following configuration options under the autolink configuration:

+ +

allowed_protocols option

+ +

This option defines which types of URLs will be autolinked. The default value of ['https', 'http', 'ftp'] means that only URLs using those protocols will be autolinked. Setting this to just ['https'] means that only HTTPS URLs will be autolinked.

+ +

default_protocol option

+ +

This option defines the default protocol for URLs that start with www. and don’t have an explicit protocol set. For example, setting this to https would convert www.example.com to https://www.example.com.

+ +

@mention-style Autolinking

+ +

As of v1.5, mention autolinking is now handled by a Mention Parser outside of this extension.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/commonmark/index.html b/2.5/extensions/commonmark/index.html new file mode 100644 index 0000000000..d075f5d732 --- /dev/null +++ b/2.5/extensions/commonmark/index.html @@ -0,0 +1,456 @@ + + + + + + + + + + + + + + + + + CommonMark Core Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

CommonMark Core Extension

+ +

The CommonMarkCoreExtension class contains all of the core Markdown syntax - things like parsing headers, code blocks, links, image, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Included by Default

+ +

This extension is automatically installed for you (behind-the-scenes) whenever you instantiate the parser using the CommonMarkConverter class:

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello World!');
+
+ +

Manual Usage

+ +

If you ever create a new Environment() from scratch, you’ll probably want to include the CommonMarkCoreExtension() so you get all the standard Markdown syntax included:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new Environment with the core extension
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Alternatively, if you don’t want all of the core Markdown syntax, avoid using CommonMarkCoreExtension. You can always add just the individual parsers, renderers, etc. you actually want with the Environment. (This is actually how the Inlines Only Extension works - it only includes a subset of things that CommonMarkCoreExtension does!)

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/default-attributes/index.html b/2.5/extensions/default-attributes/index.html new file mode 100644 index 0000000000..af0da8d31c --- /dev/null +++ b/2.5/extensions/default-attributes/index.html @@ -0,0 +1,525 @@ + + + + + + + + + + + + + + + + + Default Attributes Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Default Attributes

+ +

The DefaultAttributesExtension allows you to apply default HTML classes and other attributes using configuration options.

+ +

It works by applying the attributes to the nodes during the DocumentParsedEvent event - right after the nodes are parsed but before they are rendered. +(As a result, it’s possible that renderers may add other attributes - the goal of this extension is only to provide custom defaults.)

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DefaultAttributesExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\CommonMark\Node\Block\Heading;
+use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Extension\DefaultAttributes\DefaultAttributesExtension;
+use League\CommonMark\Extension\Table\Table;
+use League\CommonMark\MarkdownConverter;
+use League\CommonMark\Node\Block\Paragraph;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'default_attributes' => [
+        Heading::class => [
+            'class' => static function (Heading $node) {
+                if ($node->getLevel() === 1) {
+                    return 'title-main';
+                } else {
+                    return null;
+                }
+            },
+        ],
+        Table::class => [
+            'class' => 'table',
+        ],
+        Paragraph::class => [
+            'class' => ['text-center', 'font-comic-sans'],
+        ],
+        Link::class => [
+            'class' => 'btn btn-link',
+            'target' => '_blank',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new DefaultAttributesExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a default_attributes array. Each key in the array should be a FQCN for the node class you wish to apply the default attribute to, and the values should be a map of attribute names to attribute values.

+ +

Attribute values may be any of the following types:

+ +
    +
  • string
  • +
  • string[]
  • +
  • bool
  • +
  • callable (parameter is the Node, return value may be string|string[]|bool)
  • +
+ +

Examples

+ +

Here’s an example that will apply Bootstrap 4 classes and attributes:

+ +
$config = [
+    'default_attributes' => [
+        Table::class => [
+            'class' => ['table', 'table-responsive'],
+        ],
+        BlockQuote::class => [
+            'class' => 'blockquote',
+        ],
+    ],
+];
+
+ +

Here’s a more complex example that uses a callable to add a class only if the paragraph immediately follows an <h1> heading:

+ +
$config = [
+    'default_attributes' => [
+        Paragraph::class => [
+            'class' => static function (Paragraph $paragraph) {
+                if ($paragraph->previous() instanceof Heading && $paragraph->previous()->getLevel() === 1) {
+                    return 'lead';
+                }
+
+                return null;
+            },
+        ],
+    ],
+];
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/description-lists/index.html b/2.5/extensions/description-lists/index.html new file mode 100644 index 0000000000..8ac49f4c45 --- /dev/null +++ b/2.5/extensions/description-lists/index.html @@ -0,0 +1,475 @@ + + + + + + + + + + + + + + + + + Description List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Description List Extension

+ +

The DescriptionListExtension adds Markdown Extra-style description lists to facilitate the creation of <dl>, <dt>, and <dd> HTML using Markdown.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DescriptionListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\DescriptionList\DescriptionListExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DescriptionListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some markdown goes here');
+
+ +

Syntax

+ +

The syntax is based directly on the rules and logic implemented by the Markdown Extra library. Here are some examples of sample Markdown input and HTML output demonstrating the syntax:

+ +
Apple
+:   Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.
+:   An American computer company.
+
+Orange
+:   The fruit of an evergreen tree of the genus Citrus.
+
+ +
<dl>
+    <dt>Apple</dt>
+    <dd>Pomaceous fruit of plants of the genus Malus in
+    the family Rosaceae.</dd>
+    <dd>An American computer company.</dd>
+
+    <dt>Orange</dt>
+    <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
+</dl>
+
+ +

See the Markdown Extra documentation or our own spec for additional examples.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/disallowed-raw-html/index.html b/2.5/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..5fcee57d04 --- /dev/null +++ b/2.5/extensions/disallowed-raw-html/index.html @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + Disallowed Raw HTML Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Disallowed Raw HTML Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The DisallowedRawHtmlExtension automatically escapes certain HTML tags when rendering raw HTML, such as:

+ +
    +
  • <title>
  • +
  • <textarea>
  • +
  • <style>
  • +
  • <xmp>
  • +
  • <iframe>
  • +
  • <noembed>
  • +
  • <noframes>
  • +
  • <script>
  • +
  • <plaintext>
  • +
+ +

Filtering is done by replacing the leading < with the entity &lt;.

+ +

This is required by the GFM spec because these particular tags could cause undesirable side-effects if a malicious user tries to introduce them.

+ +

All other HTML tags are left untouched by this extension.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the DisallowedRawHtmlExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Customize the extension's configuration if needed
+// Default values are shown below - you can omit this configuration if you're happy with those defaults
+// and don't want to customize them
+$config = [
+    'disallowed_raw_html' => [
+        'disallowed_tags' => ['title', 'textarea', 'style', 'xmp', 'iframe', 'noembed', 'noframes', 'script', 'plaintext'],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new DisallowedRawHtmlExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I cannot change the page <title>anymore</title>');
+
+ +

Configuration

+ +

This extension can be configured by providing a disallowed_raw_html array with the following nested configuration options. The defaults are shown in the code example above.

+ +

disallowed_tags

+ +

An array containing a list of tags that should be escaped.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/embed/index.html b/2.5/extensions/embed/index.html new file mode 100644 index 0000000000..0a6c08cd23 --- /dev/null +++ b/2.5/extensions/embed/index.html @@ -0,0 +1,563 @@ + + + + + + + + + + + + + + + + + Embed Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Embed Extension

+ +

This extension can embed rich content (like videos, tweets, etc.) from other websites.

+ +

The syntax is very simple - simply place any https:// URL on its own line like this:

+ +
Check out this video!
+
+https://www.youtube.com/watch?v=dQw4w9WgXcQ
+
+ +

If the link points to embeddable content, it will be replaced with the rich HTML needed to embed it:

+ +
<p>Check out this video:</p>
+<iframe width="200" height="113" src="https://www.youtube.com/embed/dQw4w9WgXcQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

You’ll also need to install a third-party OEmbed library - see the Adapter section below.

+ +

Usage

+ +

Configure your Environment as usual and add the EmbedExtension provided by this package:

+ +
use Embed\Embed;
+use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Embed\EmbedExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'embed' => [
+        'adapter' => new OscaroteroEmbedAdapter(), // See the "Adapter" documentation below
+        'allowed_domains' => ['youtube.com', 'twitter.com', 'github.com'],
+        'fallback' => 'link',
+    ],
+];
+
+// Configure the Environment with all whatever other extensions you want
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new EmbedExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
+
+ +

Configuration

+ +

This extension supports the following configuration options under the embed configuration:

+ +

adapter option

+ +

Any instance of EmbedAdapterInterface - see the “Adapter” section below.

+ +

allowed_domains option

+ +

This option defines a list of hosts that you wish to allow embedding content from. For example, setting this to +['youtube.com'] would only allow videos from YouTube to be embedded. +It’s extremely important that you only include websites you trust since they’ll be providing HTML that is directly embedded in your website.

+ +

Any subdomains of these domains will also be allowed. For example, ['youtube.com'] would allow embedding from youtube.com or www.youtube.com.

+ +

As an additional safety measure, we recommend that you also use a Content Security Policy (CSP) +to prevent unexpected content from being embedded.

+ +

By default, this option is an empty array ([]), which means that all domains are allowed.

+ +

fallback option

+ +

This options defines the behavior when a URL cannot be embedded, either because it’s not in the list of allowed_domains, +or because the adapter could not find embeddable content for that URL.

+ +

There are two possible values for this option:

+ +
    +
  • 'link' - the URL will be kept in the document as a link (default) +-'remove' - the URL will be completely removed from the document
  • +
+ +

Adapter

+ +

league/commonmark doesn’t know how to obtain the embeddable HTML for a given URL - this must be done by an external library.

+ +

embed/embed Adapter

+ +

We do provide an adapter for the popular embed/embed library. if you’d like to use that. We like this library +because it supports fetching multiple URLs in parallel, which is ideal for performance, and it supports a wide range +of embeddable content.

+ +

To use that library, you’ll need to composer install embed/embed and then pass new OscaroteroEmbedAdapter() as the adapter +configuration option, as shown in the Usage section above.

+ +

Note: embed/embed requires a PSR-17 implementation to be installed. If you do not have one installed, the library will not work. By default these libraries are detected automatically:

+ + + +

Need to customize the maximum width/height of the embedded content? You can do that by instantiating the service provided by +embed/embed, configuring it as needed, and passing that customized instance into the adapter:

+ +
use Embed\Embed;
+use League\CommonMark\Extension\Embed\Bridge\OscaroteroEmbedAdapter;
+
+// Configure the Embed library itself
+$embedLibrary = new Embed();
+$embedLibrary->setSettings([
+    'oembed:query_parameters' => [
+        'maxwidth' => 800,
+        'maxheight' => 600,
+    ],
+    'twitch:parent' => 'example.com',
+    'facebook:token' => '1234|5678',
+    'instagram:token' => '1234|5678',
+    'twitter:token' => 'asdf',
+]);
+
+// Inject it into our adapter
+$config = [
+    'adapter' => new OscaroteroEmbedAdapter($embedLibrary),
+];
+
+// Instantiate your CommonMark environment and converter like usual
+// ...
+
+ +

Custom Adapter

+ +

If you prefer to use a different library, you’ll need to implement our EmbedAdapterInterface yourself with +whatever OEmbed library you choose.

+ +

Tips

+ +

If you need to wrap the HTML in a container tag, consider using the HtmlDecorator renderer:

+ +
$environment->addRenderer(Embed::class, new HtmlDecorator(new EmbedRenderer(), 'div', ['class' => 'embeded-content']));
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/external-links/index.html b/2.5/extensions/external-links/index.html new file mode 100644 index 0000000000..e49b7a3d0a --- /dev/null +++ b/2.5/extensions/external-links/index.html @@ -0,0 +1,560 @@ + + + + + + + + + + + + + + + + + External Links Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

External Links Extension

+ +

This extension can detect links to external sites and adjust the markup accordingly:

+ +
    +
  • Make the links open in new tabs/windows
  • +
  • Adds a rel attribute to the resulting <a> tag with values like "nofollow noopener noreferrer"
  • +
  • Optionally adds any custom HTML classes
  • +
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the ExternalLinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\ExternalLink\ExternalLinkExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'external_link' => [
+        'internal_hosts' => 'www.example.com', // TODO: Don't forget to set this!
+        'open_in_new_window' => true,
+        'html_class' => 'external-link',
+        'nofollow' => '',
+        'noopener' => 'external',
+        'noreferrer' => 'external',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new ExternalLinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('I successfully installed the <https://github.com/thephpleague/commonmark> project!');
+
+ +

Configuration

+ +

This extension supports three configuration options under the external_link configuration:

+ +

internal_hosts

+ +

This option defines a list of hosts which are considered non-external and should not receive the external link treatment.

+ +

This can be a single host name, like 'example.com', which must match exactly.

+ +

Wildcard matching is also supported using regular expression like '/(^|\.)example\.com$/'. Note that you must use / characters to delimit your regex.

+ +

This configuration option also accepts an array of multiple strings and/or regexes:

+ +
$config = [
+    'external_link' => [
+        'internal_hosts' => ['foo.example.com', 'bar.example.com', '/(^|\.)google\.com$/],
+    ],
+];
+
+ +

By default, if this option is not provided, all links will be considered external.

+ +

open_in_new_window

+ +

This option (which defaults to false) determines whether any external links should open in a new tab/window.

+ +

html_class

+ +

This option allows you to provide a string containing one or more HTML classes that should be added to the external link <a> tags: No classes are added by default.

+ +

nofollow, noopener, and noreferrer

+ +

These options allow you to configure whether a rel attribute should be applied to links. Each of these options can be set to one of the following string values:

+ +
    +
  • 'external' - Apply to external links only
  • +
  • 'internal' - Apply to internal links only
  • +
  • 'all' - Apply to all links (both internal and external)
  • +
  • '' (empty string) - Don’t apply to any links
  • +
+ +

Unless you override these options, nofollow defaults to '' and the others default to 'external'.

+ +

Advanced Rendering

+ +

When an external link is detected, the ExternalLinkProcessor will set the external data option on the Link node to either true or false. You can therefore create a custom link renderer which checks this value and behaves accordingly:

+ +
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
+use League\CommonMark\Renderer\ChildNodeRendererInterface;
+use League\CommonMark\Renderer\NodeRendererInterface;
+use League\CommonMark\Util\HtmlElement;
+
+class MyCustomLinkRenderer implements NodeRendererInterface
+{
+    /**
+     * @param Node                       $node
+     * @param ChildNodeRendererInterface $childRenderer
+     *
+     * @return HtmlElement
+     */
+    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
+    {
+        Link::assertInstanceOf($node);
+
+        if ($node->data->get('external')) {
+            // This is an external link - render it accordingly
+        } else {
+            // This is an internal link
+        }
+
+        // ...
+    }
+}
+
+ +

Adding Icons

+ +

You can also use CSS to automagically add an external link icon by targeting the html_class given in the configuration:

+ +
// Font Awesome example:
+a[target="_blank"]::after,
+a.external::after {
+   content: "\f08e";
+   font: normal normal normal 14px/1 FontAwesome;
+}
+
+// Glyphicon example:
+a[target="_blank"]::after,
+a.external::after {
+  @extend .glyphicon;
+  content: "\e164";
+  margin-left: .5em;
+  margin-right: .25em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/footnotes/index.html b/2.5/extensions/footnotes/index.html new file mode 100644 index 0000000000..9d7f7125f9 --- /dev/null +++ b/2.5/extensions/footnotes/index.html @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + Footnote Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Footnotes

+ +

The FootnoteExtension adds the ability to create footnotes in Markdown documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Footnote Syntax

+ +

Sample Markdown input:

+ +
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi[^note1] leo risus, porta ac consectetur ac.
+
+[^note1]: Elit Malesuada Ridiculus
+
+ +

Result:

+ +
<p>
+    Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
+    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+    Morbi<sup id="fnref:note1"><a class="footnote-ref" href="#fn:note1" role="doc-noteref">1</a></sup> leo risus, porta ac consectetur ac.
+</p>
+<div class="footnotes">
+    <hr />
+    <ol>
+        <li class="footnote" id="fn:note1">
+            <p>
+                Elit Malesuada Ridiculus <a class="footnote-backref" rev="footnote" href="#fnref:note1"></a>
+            </p>
+        </li>
+    </ol>
+</div>
+
+ +

Usage

+ +

Configure your Environment as usual and simply add the FootnoteExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Footnote\FootnoteExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'footnote' => [
+        'backref_class'      => 'footnote-backref',
+        'backref_symbol'     => '↩',
+        'container_add_hr'   => true,
+        'container_class'    => 'footnotes',
+        'ref_class'          => 'footnote-ref',
+        'ref_id_prefix'      => 'fnref:',
+        'footnote_class'     => 'footnote',
+        'footnote_id_prefix' => 'fn:',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FootnoteExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert("This is a footnote[^test] test.\n\n[^test]: Doesn't it look good!");
+
+ +

Configuration

+ +

This extension can be configured by providing a footnote array with several nested configuration options. The defaults are shown in the code example above.

+ +

backref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote backreference elements.

+ +

backref_symbol

+ +

This string option sets the symbol used as the contents of the footnote backreference link. It defaults to \League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer::DEFAULT_SYMBOL = '↩'.

+ +

If you want to use a custom icon, set this to an empty string '' and take a look at the Adding Icons section below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

container_add_hr

+ +

This boolean option controls whether an <hr> element should be added inside the container. Set this to false if you want more control over how the footnote section at the bottom is differentiated from the rest of the document.

+ +

container_class

+ +

This string option defines which HTML class should be assigned to the container at the bottom of the page which shows all the footnotes.

+ +

ref_class

+ +

This string option defines which HTML class should be assigned to rendered footnote reference elements.

+ +

ref_id_prefix

+ +

This string option defines the prefix prepended to footnote references.

+ +

footnote_class

+ +

This string option defines which HTML class should be assigned to rendered footnote elements.

+ +

footnote_id_prefix

+ +

This string option defines the prefix prepended to footnote elements.

+ +

Adding Icons

+ +

You can use CSS to add a custom icon instead of providing a backref_symbol:

+ +
$config = [
+    'footnote' => [
+        'backref_class' => 'footnote-backref',
+        'backref_symbol' => '',
+    ],
+];
+
+ +

Then target the backref_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.footnote-backref::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/front-matter/index.html b/2.5/extensions/front-matter/index.html new file mode 100644 index 0000000000..8d6664f596 --- /dev/null +++ b/2.5/extensions/front-matter/index.html @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + Front Matter Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Front Matter Extension

+ +

The FrontMatterExtension adds the ability to parse YAML front matter from the Markdown document and include that in the return result.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

You will also need to install symfony/yaml or the YAML extension for PHP to use this extension. For symfony/yaml:

+ +
composer require symfony/yaml
+
+ +

(You can use any version of symfony/yaml 2.5 or higher, though we recommend using 4.0 or higher.)

+ +

Front Matter Syntax

+ +

This extension follows the Jekyll Front Matter syntax. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Here is a basic example:

+ +
---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+
+ +

This will produce a front matter array similar to this:

+ +
$parsedFrontMatter = [
+    'layout' => 'post',
+    'title' => 'I Love Markdown',
+    'tags' => [
+        'test',
+        'example',
+    ],
+];
+
+ +

And the HTML output will only contain the one heading:

+ +
<h1>Hello World!</h1>
+
+ +

Usage

+ +

Configure your Environment as usual and add the FrontMatterExtension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the extension
+$environment->addExtension(new FrontMatterExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+// A sample Markdown file with some front matter:
+$markdown = <<<MD
+---
+layout: post
+title: I Love Markdown
+tags:
+  - test
+  - example
+---
+
+# Hello World!
+MD;
+
+$result = $converter->convert($markdown);
+
+// Grab the front matter:
+if ($result instanceof RenderedContentWithFrontMatter) {
+    $frontMatter = $result->getFrontMatter();
+}
+
+// Output the HTML using any of these:
+echo $result;               // implicit string cast
+// or:
+echo (string) $result;      // explicit string cast
+// or:
+echo $result->getContent();
+
+ +

Parsing Front Matter Only

+ +

You don’t have to parse the entire file (including all the Markdown) if you only want the front matter. You can either instantiate the front matter parser yourself and call it directly, like this:

+ +
use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
+use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+// For `symfony/yaml`
+$frontMatterParser = new FrontMatterParser(new SymfonyYamlFrontMatterParser());
+// For YAML extension
+$frontMatterParser = new FrontMatterParser(new LibYamlFrontMatterParser());
+$result = $frontMatterParser->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

Or you can use the getFrontMatterParser() method from the extension:

+ +
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
+
+$markdown = '...'; // TODO: Load some Markdown content somehow
+
+$frontMatterExtension = new FrontMatterExtension();
+$result = $frontMatterExtension->getFrontMatterParser()->parse($markdown);
+
+var_dump($result->getFrontMatter()); // The parsed front matter
+var_dump($result->getContent()); // Markdown content without the front matter
+
+ +

This latter approach may be more convenient if you have already instantiated a FrontMatterExtension object you’re adding to the Environment somewhere and just want to call that.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/github-flavored-markdown/index.html b/2.5/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..e45769c506 --- /dev/null +++ b/2.5/extensions/github-flavored-markdown/index.html @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + GitHub-Flavored Markdown - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

GitHub-Flavored Markdown

+ +

You can manually add the GFM extension to your environment like this:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark and GFM parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This will automatically include all of these sub-extensions/features for you:

+ + + +

Or, if you only want a subset of GFM extensions, you can add them individually like this instead:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Remove any of the lines below if you don't want a particular feature
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+$environment->addExtension(new TaskListExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello GFM!');
+
+ +

This extension relies on the CommonMarkCoreExtension being enabled, so don’t forget to include that too.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/heading-permalinks/index.html b/2.5/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..6e4f0e4d60 --- /dev/null +++ b/2.5/extensions/heading-permalinks/index.html @@ -0,0 +1,631 @@ + + + + + + + + + + + + + + + + + Heading Permalink Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Heading Permalink Extension

+ +

This extension makes all of your heading elements (<h1>, <h2>, etc) linkable so that users can quickly grab a link to that specific part of the document - almost like the headings in this documentation!

+ +

Tip: You can combine this with the Table of Contents extension to automatically generate a list of links to the headings in your documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
+use League\CommonMark\MarkdownConverter;
+
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'id_prefix' => 'content',
+        'apply_id_to_heading' => false,
+        'heading_class' => '',
+        'fragment_prefix' => 'content',
+        'insert' => 'before',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'title' => 'Permalink',
+        'symbol' => HeadingPermalinkRenderer::DEFAULT_SYMBOL,
+        'aria_hidden' => true,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new HeadingPermalinkExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ +

Configuration

+ +

This extension can be configured by providing a heading_permalink array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <a> tag’s class attribute. This defaults to 'heading-permalink'.

+ +

id_prefix

+ +

This should be a string you want prepended to HTML IDs. This prevents generating HTML ID attributes which might conflict with others in your stylesheet. A dash separator (-) will be added between the prefix and the ID. You can instead set this to an empty string ('') if you don’t want a prefix.

+ +

apply_id_to_heading

+ +

If this value is true, the id attributes will be written to the <h> tag instead of the <a>.

+ +

heading_class

+ +

The class will be added to the <h> tag (no matter if apply_id_to_heading is set true or false)

+ +

fragment_prefix

+ +

This should be a string you want prepended to the URL fragment in the link’s href attribute. This should typically be set to the same value as id_prefix for links to work properly. However, you may not want to expose that same prefix in your URLs - in that case, you can set this to something different (even an empty string) and use JavaScript to “rewrite” them.

+ +

For example, to emulate how GitHub heading permalinks work, set id_prefix to 'user-content', set fragment_prefix to '', and insert some JavaScript into the page like this:

+ +
var scrollToPermalink = function() {
+    var link = document.getElementById('user-content-' + window.location.hash);
+    if (link) {
+        link.scrollIntoView({behavior: 'smooth'});
+    }
+};
+
+window.addEventListener('hashchange', scrollToPermalink);
+if (window.location.hash) {
+    scrollToPermalink();
+}
+
+ +

insert

+ +

This controls whether the anchor is added to the beginning of the heading tag (before), the end of the tag (after), or not added at all (none).

+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should have permalinks added. By default, all 6 levels (1, 2, 3, 4, 5, and 6) will have them. You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

symbol

+ +

This option sets the symbol used to display the permalink on the document. This defaults to \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_SYMBOL = '¶'.

+ +

If you want to use a custom icon, then set this to an empty string '' and check out the Adding Icons sections below.

+ +
+

Note: Special HTML characters (" & < >) provided here will be escaped for security reasons.

+
+ +

title

+ +

This option sets the title attribute on the <a> tag. This defaults to 'Permalink'.

+ +

aria_hidden

+ +

This option sets the aria-hidden attribute on the <a> tag. This defaults to aria-hidden="true".

+ +

Setting this option to false would render the <a> tag excluding the aria-hidden entirely.

+ +

Example

+ +

If you wanted to style your headings exactly like this documentation page does, try this configuration!

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'insert' => 'after',
+        'symbol' => '¶',
+        'title' => "Permalink",
+    ],
+];
+
+ +

Along with this CSS:

+ +
.heading-permalink {
+    font-size: .8em;
+    vertical-align: super;
+    text-decoration: none;
+    color: transparent;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink,
+.heading-permalink:hover {
+    text-decoration: none;
+    color: #777;
+}
+
+ +

Styling Ideas

+ +

This library doesn’t provide any CSS styling for the anchor element(s), but here are some ideas you could use in your own stylesheet.

+ +

You could hide the icon until the user hovers over the heading:

+ +
.heading-permalink {
+  visibility: hidden;
+}
+
+h1:hover .heading-permalink,
+h2:hover .heading-permalink,
+h3:hover .heading-permalink,
+h4:hover .heading-permalink,
+h5:hover .heading-permalink,
+h6:hover .heading-permalink
+{
+  visibility: visible;
+}
+
+ +

You could also float the symbol just a little bit left of the heading:

+ +
.heading-permalink {
+  float: left;
+  padding-right: 4px;
+  margin-left: -20px;
+  line-height: 1;
+}
+
+ +

These are only ideas - feel free to customize this however you’d like!

+ +

Adding Icons

+ +

You can also use CSS to add a custom icon instead of providing a symbol:

+ +
$config = [
+    'heading_permalink' => [
+        'html_class' => 'heading-permalink',
+        'symbol' => '',
+    ],
+];
+
+ +

Then targeting the html_class given in the configuration in your CSS:

+ +
/**
+ * Custom SVG Icon.
+ */
+.heading-permalink::after {
+  display: inline-block;
+  content: "";
+  /**
+   * Octicon Link (https://iconify.design/icon-sets/octicon/link.html)
+   * [Pro Tip] Use an SVG URL encoder (https://yoksel.github.io/url-encoder).
+   */
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' style='-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg)' viewBox='0 0 16 16' transform='rotate(360)'%3E%3Cpath fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z' fill='%23626262'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-size: 1em;
+}
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/inlines-only/index.html b/2.5/extensions/inlines-only/index.html new file mode 100644 index 0000000000..7f3b78786d --- /dev/null +++ b/2.5/extensions/inlines-only/index.html @@ -0,0 +1,446 @@ + + + + + + + + + + + + + + + + + Inlines Only Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Inlines Only Extension

+ +

This extension configures the parser to only render inline elements - no paragraph tags, headers, code blocks, etc. This makes it perfect for commenting systems where you only want users having bold, italics, links, etc.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Although you normally add extra extensions along with the default CommonMark Core extension, we’re not going to do that here, because this is essentially a slimmed-down version of the core extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\InlinesOnly\InlinesOnlyExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Create a new, empty environment
+$environment = new Environment($config);
+
+// Add this extension
+$environment->addExtension(new InlinesOnlyExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('**Hello World!**');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/mentions/index.html b/2.5/extensions/mentions/index.html new file mode 100644 index 0000000000..a852de7fbc --- /dev/null +++ b/2.5/extensions/mentions/index.html @@ -0,0 +1,672 @@ + + + + + + + + + + + + + + + + + Mention Parser - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Mention Extension

+ +

The MentionExtension makes it easy to parse shortened mentions and references like @colinodell to a Twitter URL +or #123 to a GitHub issue URL. You can create your own custom syntax by defining which prefix you want to use and +how to generate the corresponding URL.

+ +

Usage

+ +

You can create your own custom syntax by supplying the configuration with an array of options that +define the starting prefix, a regular expression to match against, and any custom URL template or callable to +generate the URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        // GitHub handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.github.com/colinodell">@colinodell</a>`
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            'generator' => 'https://github.com/%s',
+        ],
+        // GitHub issue mention configuration.
+        // Sample Input:  `#473`
+        // Sample Output: `<a href="https://github.com/thephpleague/commonmark/issues/473">#473</a>`
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            'generator' => "https://github.com/thephpleague/commonmark/issues/%d",
+        ],
+        // Twitter handler mention configuration.
+        // Sample Input:  `@colinodell`
+        // Sample Output: `<a href="https://www.twitter.com/colinodell">@colinodell</a>`
+        // Note: when registering more than one mention parser with the same prefix, the first mention parser to
+        // successfully match and return a properly constructed Mention object (where the URL has been set) will be the
+        // the mention parser that is used. In this example, the GitHub handle would actually match first because
+        // there isn't any real validation to check whether https://www.github.com/colinodell exists. However, in
+        // CMS applications, you could check whether its a local user first, then check Twitter and then GitHub, etc.
+        'twitter_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[A-Za-z0-9_]{1,15}(?!\w)',
+            'generator' => 'https://twitter.com/%s',
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on GitHub: @colinodell');
+// Output:
+// <p>Follow me on GitHub: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

String-Based URL Templates

+ +

URL templates are perfect for situations where the identifier is inserted directly into a URL:

+ +
"@colinodell" => https://www.twitter.com/colinodell
+ ▲└────┬───┘                             └───┬────┘
+ │     │                                     │
+Prefix └───────────── Identifier ────────────┘
+
+ +

Examples of using string-based URL templates can be seen in the usage example above - you simply provide a string to the generator option.

+ +

Note that the URL template must be a string, and that the %s placeholder will be replaced by whatever the user enters after the prefix (in this case, @). You can use any prefix, regular expression pattern (without opening/closing delimiter or modifiers), or URL template you want!

+ +

Custom Callback-Based Parsers

+ +

Need more power than simply adding the mention inside a string based URL template? The MentionExtension automatically +detects if the provided generator is an object that implements \League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface +or a valid PHP callable that can generate a +resulting URL.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\Node\Inline\AbstractInline;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'github_handle' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)',
+            // The recommended approach is to provide a class that implements MentionGeneratorInterface.
+            'generator' => new GithubUserMentionGenerator(), // TODO: Implement such a class yourself
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Alternatively, if your logic is simple, you can implement an inline anonymous class like this example.
+            'generator' => new class implements MentionGeneratorInterface {
+                 public function generateMention(Mention $mention): ?AbstractInline
+                 {
+                     $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                     return $mention;
+                 }
+             },
+        ],
+        'github_issue' => [
+            'prefix'    => '#',
+            'pattern'   => '\d+',
+            // Any type of callable, including anonymous closures, (with optional typehints) are also supported.
+            // This allows for better compatibility between different major versions of CommonMark.
+            // However, you sacrifice the ability to type-check which means automated development tools
+            // may not notice if your code is no longer compatible with new versions - you'll need to
+            // manually verify this yourself.
+            'generator' => function ($mention) {
+                // Immediately return if not passed the supported Mention object.
+                // This is an example of the types of manual checks you'll need to perform if not using type hints
+                if (!($mention instanceof Mention)) {
+                    return null;
+                }
+
+                $mention->setUrl(\sprintf('https://github.com/thephpleague/commonmark/issues/%d', $mention->getIdentifier()));
+
+                return $mention;
+            },
+        ],
+
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Follow me on Twitter: @colinodell');
+// Output:
+// <p>Follow me on Twitter: <a href="https://www.github.com/colinodell">@colinodell</a></p>
+
+ +

When implementing MentionGeneratorInterface or a simple callable, you’ll receive a single Mention parameter and must either:

+ +
    +
  • Return the same passed Mention object along with setting the URL; or,
  • +
  • Return a new object that extends \League\CommonMark\Inline\Element\AbstractInline; or,
  • +
  • Return null (and not set a URL on the Mention object) if the mention isn’t a match and should be skipped; not parsed.
  • +
+ +

Here’s a faux-real-world example of how you might use such a generator for your application. Imagine you +want to parse @username into custom user profile links for your application, but only if the user exists. You could +create a class like the following which integrates with the framework your application is built on:

+ +
use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
+use League\CommonMark\Extension\Mention\Mention;
+use League\CommonMark\Inline\Element\AbstractInline;
+
+class UserMentionGenerator implements MentionGeneratorInterface
+{
+    private $currentUser;
+    private $userRepository;
+    private $router;
+
+    public function __construct (AccountInterface $currentUser, UserRepository $userRepository, Router $router)
+    {
+        $this->currentUser = $currentUser;
+        $this->userRepository = $userRepository;
+        $this->router = $router;
+    }
+
+    public function generateMention(Mention $mention): ?AbstractInline
+    {
+        // Determine mention visibility (i.e. member privacy).
+        if (!$this->currentUser->hasPermission('access profiles')) {
+            $emphasis = new \League\CommonMark\Inline\Element\Emphasis();
+            $emphasis->appendChild(new \League\CommonMark\Inline\Element\Text('[members only]'));
+            return $emphasis;
+        }
+
+        // Locate the user that is mentioned.
+        $user = $this->userRepository->findUser($mention->getIdentifier());
+
+        // The mention isn't valid if the user does not exist.
+        if (!$user) {
+            return null;
+        }
+
+        // Change the label.
+        $mention->setLabel($user->getFullName());
+        // Use the path to their profile as the URL, typecasting to a string in case the service returns
+        // a __toString object; otherwise you will need to figure out a way to extract the string URL
+        // from the service.
+        $mention->setUrl((string) $this->router->generate('user_profile', ['id' => $user->getId()]));
+
+        return $mention;
+    }
+}
+
+ +

You can then hook this class up to a mention definition in the configuration to generate profile URLs from Markdown +mentions:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Mention\MentionExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Grab your UserMentionGenerator somehow, perhaps from a DI container or instantiate it if needed
+$userMentionGenerator = $container->get(UserMentionGenerator::class);
+
+// Define your configuration
+$config = [
+    'mentions' => [
+        'user_url_generator' => [
+            'prefix'    => '@',
+            'pattern'   => '[a-z0-9]+',
+            'generator' => $userMentionGenerator,
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the Mention extension.
+$environment->addExtension(new MentionExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('You should ask @colinodell about that');
+
+// Output (if current user has permission to view profiles):
+// <p>You should ask <a href="/user/123/profile">Colin O'Dell</a> about that</p>
+//
+// Output (if current user doesn't have has access to view profiles):
+// <p>You should ask <em>[members only]</em> about that</p>
+
+ +

Rendering

+ +

Whenever a mention is found, a Mention object is added to the document’s AST. +This object extends from Link, so it’ll be rendered as a normal <a> tag by default.

+ +

If you need more control over the output you can implement a custom renderer for the Mention type +and convert it to whatever HTML you wish!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/overview/index.html b/2.5/extensions/overview/index.html new file mode 100644 index 0000000000..7c7648f6b3 --- /dev/null +++ b/2.5/extensions/overview/index.html @@ -0,0 +1,604 @@ + + + + + + + + + + + + + + + + + Extensions Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Extensions Overview

+ +

Extensions provide a simple way to add new syntax and features to the CommonMark parser.

+ +

Included Extensions

+ +

Starting with version 1.3.0, this library includes several extensions to support GitHub Flavored Markdown (GFM) and +many other common use-cases. Most of these extensions started out as 3rd-party community based extensions that have +since been officially adopted by this library in an effort to ensure future compatibility and to provide an easy way +to enhance your experience out-of-the-box depending on your specific use-cases.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ExtensionPurposeVersion IntroducedGFM
AttributesAdd HTML attributes (like id and class) from within the Markdown content1.5.0 
AutolinksEnables automatic linking of URLs within text without needing to wrap them with Markdown syntax1.3.0
Default AttributesEasily apply default HTML classes using configuration options to match your site’s styles2.0.0 
Description ListsCreate <dl> description lists using Markdown Extra’s syntax2.0.0 
Disallowed Raw HTMLDisables certain kinds of HTML tags that could affect page rendering1.3.0
EmbedEmbed rich content (like videos, tweets, and more) from other websites2.3.0 
External LinksTags external links with additional markup1.3.0 
FootnotesAdd footnote references throughout the document and show a listing of them at the bottom1.5.0 
Front MatterParses YAML front matter from your Markdown input2.0.0 
GitHub Flavored MarkdownEnables full support for GFM. Automatically includes the extensions noted in the GFM column (though you can certainly add them individually if you wish):1.3.0 
Heading PermalinksMakes heading elements linkable1.4.0 
Inlines OnlyOnly includes standard CommonMark inline elements - perfect for handling comments and other short bits of text where you only want bold, italic, links, etc.1.3.0 
MentionsEasy parsing of @mention and #123-style references1.5.0 
StrikethroughAllows using tilde characters (~~) for ~strikethrough~ formatting1.3.0
TablesEnables you to create HTML tables1.3.0
Table of ContentsAutomatically inserts links to the headings at the top of your document1.4.0 
Task ListsAllows the creation of task lists1.3.0
Smart PunctuationIntelligently converts ASCII quotes, dashes, and ellipses to their fancy Unicode equivalents1.3.0 
+ +

Usage

+ +

You can enable extensions by simply calling ->addExtension() on the Environment.

+ +

In an effort to streamline the extensions used in GitHub Flavored Markdown (GFM), a special extension named +GithubFlavoredMarkdownExtension can be used that will automatically add all the extensions checked in the GFM +column above for you:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the extensions you need
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+$environment->addExtension(new GithubFlavoredMarkdownExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

Or maybe you only want a subset of GFM extensions, plus the Smart Punctuation extension:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\Autolink\AutolinkExtension;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the other extensions you need
+$environment->addExtension(new AutolinkExtension());
+$environment->addExtension(new DisallowedRawHtmlExtension());
+$environment->addExtension(new SmartPunctExtension());
+$environment->addExtension(new StrikethroughExtension());
+$environment->addExtension(new TableExtension());
+
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Hello World!');
+
+ +

The extension system makes it easy to mix-and-match extensions to fit your needs.

+ +

Writing Custom Extensions

+ +

See the Custom Extensions page for details on how you can create your own custom extensions.

+ + + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/smart-punctuation/index.html b/2.5/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..4d45dfcc7b --- /dev/null +++ b/2.5/extensions/smart-punctuation/index.html @@ -0,0 +1,465 @@ + + + + + + + + + + + + + + + + + Smart Punctuation Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Smart Punctuation Extension

+ +

The SmartPunctExtension Intelligently converts ASCII quotes, dashes, and ellipses to their Unicode equivalents.

+ +

For example, this Markdown…

+ +
"CommonMark is the PHP League's Markdown parser," she said.  "It's super-configurable... you can even use additional extensions to expand its capabilities -- just like this one!"
+
+ +

Will result in this HTML:

+ +
<p>“CommonMark is the PHP League’s Markdown parser,” she said.  “It’s super-configurable… you can even use additional extensions to expand its capabilities – just like this one!”</p>
+
+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Extensions can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\SmartPunct\SmartPunctExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'smartpunct' => [
+        'double_quote_opener' => '“',
+        'double_quote_closer' => '”',
+        'single_quote_opener' => '‘',
+        'single_quote_closer' => '’',
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new SmartPunctExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Hello World!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/strikethrough/index.html b/2.5/extensions/strikethrough/index.html new file mode 100644 index 0000000000..5af93e025b --- /dev/null +++ b/2.5/extensions/strikethrough/index.html @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + Strikethrough Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Strikethrough Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style strikethrough syntax. It allows users to use ~~ in order to indicate text that should be rendered within <del> tags.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

This extension can be added to any new Environment:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Strikethrough\StrikethroughExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new StrikethroughExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('This extension is ~~really good~~ great!');
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/table-of-contents/index.html b/2.5/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..887672866a --- /dev/null +++ b/2.5/extensions/table-of-contents/index.html @@ -0,0 +1,603 @@ + + + + + + + + + + + + + + + + + Table of Contents Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Table of Contents Extension

+ +

The TableOfContentsExtension automatically inserts a table of contents into your document with links to the various headings.

+ +

The Heading Permalink extension must also be included for this to work.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableOfContentsExtension and HeadingPermalinkExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
+use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+// Extension defaults are shown below
+// If you're happy with the defaults, feel free to remove them from this array
+$config = [
+    'table_of_contents' => [
+        'html_class' => 'table-of-contents',
+        'position' => 'top',
+        'style' => 'bullet',
+        'min_heading_level' => 1,
+        'max_heading_level' => 6,
+        'normalize' => 'relative',
+        'placeholder' => null,
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add the two extensions
+$environment->addExtension(new HeadingPermalinkExtension());
+$environment->addExtension(new TableOfContentsExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('# Awesome!');
+
+ +

Configuration

+ +

This extension can be configured by providing a table_of_contents array with several nested configuration options. The defaults are shown in the code example above.

+ +

html_class

+ +

The value of this nested configuration option should be a string that you want set as the <ul> or <ol> tag’s class attribute. This defaults to 'table-of-contents'.

+ +

normalize

+ +

This should be a string that defines one of three different strategies to use when generating a (potentially-nested) list from your various headings:

+ +
    +
  • 'flat'
  • +
  • 'as-is'
  • +
  • 'relative' (default)
  • +
+ +

See “Normalization Strategies” below for more information.

+ +

position

+ +

This string controls where in the document your table of contents will be placed. There are two options:

+ +
    +
  • 'top' (default) - Insert at the very top of the document, before any content
  • +
  • 'before-headings' - Insert just before the very first heading - useful if you want to have some descriptive text show above the table of content.
  • +
  • 'placeholder' - Location is manually defined by a user-provided placeholder somewhere in the document (see the placeholder option below)
  • +
+ +

If you’d like to customize this further, you can implement a custom event listener to locate the TableOfContents node and reposition it somewhere else in the document prior to rendering.

+ +

placeholder

+ +

When combined with 'position' => 'placeholder', this setting tells the extension which placeholder content should be replaced with the Table of Contents. For example, if you set this option to [TOC], then any lines in your document consisting of that [TOC] placeholder will be replaced by the Table of Contents. Note that this option has no default value - you must provide this string yourself.

+ +

style

+ +

This string option controls what style of HTML list should be used to render the table of contents:

+ +
    +
  • 'bullet' (default) - Unordered, bulleted list (<ul>)
  • +
  • 'ordered' - Ordered list (<ol>)
  • +
+ +

min_heading_level and max_heading_level

+ +

These two settings control which headings should appear in the list. By default, all 6 levels (1, 2, 3, 4, 5, and 6). You can override this by setting the min_heading_level and/or max_heading_level to a different number (int value).

+ +

Normalization Strategies

+ +

Consider this sample Markdown input:

+ +
## Level 2 Heading
+
+This is a sample document that starts with a level 2 heading
+
+#### Level 4 Heading
+
+Notice how we went from a level 2 heading to a level 4 heading!
+
+### Level 3 Heading
+
+And now we have a level 3 heading here.
+
+ +

Here’s how the different normalization strategies would handle this input:

+ +

Strategy: 'flat'

+ +

All links in your table of contents will be shown in a flat, single-level list:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-4-heading">Level 4 Heading</a></p>
+    </li>
+    <li>
+        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'as-is'

+ +

Level 1 headings (<h1>) will appear on the first level of the list, with level 2 headings (<h2>) nested under those, and so forth - exactly as they occur within the document. But this can get weird if your document doesn’t start with level 1 headings, or it doesn’t properly nest the levels:

+ +
<ul class="table-of-contents">
+    <li>
+        <ul>
+            <li>
+                <p><a href="#level-2-heading">Level 2 Heading</a></p>
+                <ul>
+                    <li>
+                        <ul>
+                            <li>
+                                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+                            </li>
+                        </ul>
+                    </li>
+                    <li>
+                        <p><a href="#level-3-heading">Level 3 Heading</a></p>
+                    </li>
+                </ul>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ +

Strategy: 'relative'

+ +

Applies nesting, but handles edge cases (like incorrect nesting levels) as you’d expect:

+ +
<ul class="table-of-contents">
+    <li>
+        <p><a href="#level-2-heading">Level 2 Heading</a></p>
+        <ul>
+            <li>
+                <p><a href="#level-4-heading">Level 4 Heading</a></p>
+            </li>
+        </ul>
+        <ul>
+            <li>
+                <p><a href="#level-3-heading">Level 3 Heading</a></p>
+            </li>
+        </ul>
+    </li>
+</ul>
+
+<!-- The rest of the content would go here -->
+
+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/tables/index.html b/2.5/extensions/tables/index.html new file mode 100644 index 0000000000..67115bbb51 --- /dev/null +++ b/2.5/extensions/tables/index.html @@ -0,0 +1,563 @@ + + + + + + + + + + + + + + + + + Table Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Table Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

The TableExtension adds the ability to create tables in CommonMark documents.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TableExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\Table\TableExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => false,
+            'tag' => 'div',
+            'attributes' => [],
+        ],
+        'alignment_attributes' => [
+            'left'   => ['align' => 'left'],
+            'center' => ['align' => 'center'],
+            'right'  => ['align' => 'right'],
+        ],
+    ],
+];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TableExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+echo $converter->convert('Some Markdown with a table in it');
+
+ +

Syntax

+ +

This package is fully compatible with GFM-style tables:

+ +

Simple

+ +

Code:

+ +
th | th(center) | th(right)
+---|:----------:|----------:
+td | td         | td
+
+ +

Result:

+ +
<table>
+<thead>
+<tr>
+<th align="left">th</th>
+<th align="center">th(center)</th>
+<th align="right">th(right)/th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td align="left">td</td>
+<td align="center">td</td>
+<td align="right">td</td>
+</tr>
+</tbody>
+</table>
+
+ +

Advanced

+ +
| header 1 | header 2 | header 2 |
+| :------- | :------: | -------: |
+| cell 1.1 | cell 1.2 | cell 1.3 |
+| cell 2.1 | cell 2.2 | cell 2.3 |
+
+ +

Configuration

+ +

Wrapping Container

+ +

You can “wrap” the table with a container element by configuring the following options:

+ +
    +
  • enabled: (boolean) Whether to wrap the table with a container element. Defaults to false.
  • +
  • tag: (string) The tag name of the container element. Defaults to div.
  • +
  • attributes: (array) An array of attributes to apply to the container element. Defaults to [].
  • +
+ +

For example, to wrap all tables within a <div class="table-responsive"> container element:

+ +
$config = [
+    'table' => [
+        'wrap' => [
+            'enabled' => true,
+            'tag' => 'div',
+            'attributes' => ['class' => 'table-responsive'],
+        ],
+    ],
+];
+
+ +

Alignment

+ +

You can configure the HTML attributes used for alignment via the alignment_attributes option. For example, if you wanted Bootstrap classes to be applied, you could do so like this:

+ +
$config = [
+    'table' => [
+        'alignment_attributes' => [
+            'left' => ['class' => 'text-start'],
+            'center' => ['class' => 'text-center'],
+            'right' => ['class' => 'text-end'],
+        ],
+    ],
+];
+
+ +

Or you could use inline styles:

+ +
$config = [
+    'table' => [
+        'alignment_attributes' => [
+            'left' => ['style' => 'text-align:left'],
+            'center' => ['style' => 'text-align:center'],
+            'right' => ['style' => 'text-align:right'],
+        ],
+    ],
+];
+
+ +

Or any other HTML attributes you’d like!

+ +

Credits

+ +

The Table functionality was originally built by Martin Hasoň and Webuni s.r.o. before it was merged into the core parser.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/extensions/task-lists/index.html b/2.5/extensions/task-lists/index.html new file mode 100644 index 0000000000..9b0bcd1016 --- /dev/null +++ b/2.5/extensions/task-lists/index.html @@ -0,0 +1,459 @@ + + + + + + + + + + + + + + + + + Task List Extension - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Task List Extension

+ +

(Note: this extension is included by default within the GFM extension)

+ +

This extension adds support for GFM-style task lists.

+ +

Installation

+ +

This extension is bundled with league/commonmark. This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Usage

+ +

Configure your Environment as usual and simply add the TaskListExtension provided by this package:

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Extension\TaskList\TaskListExtension;
+use League\CommonMark\MarkdownConverter;
+
+// Define your configuration, if needed
+$config = [];
+
+// Configure the Environment with all the CommonMark parsers/renderers
+$environment = new Environment($config);
+$environment->addExtension(new CommonMarkCoreExtension());
+
+// Add this extension
+$environment->addExtension(new TaskListExtension());
+
+// Instantiate the converter engine and start converting some Markdown!
+$converter = new MarkdownConverter($environment);
+
+$markdown = <<<EOT
+ - [x] Install this extension
+ - [ ] ???
+ - [ ] Profit!
+EOT;
+
+echo $converter->convert($markdown);
+
+ +

Please note that this extension doesn’t provide any JavaScript functionality to handle people checking and unchecking boxes - you’ll need to implement that yourself if needed.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/index.html b/2.5/index.html new file mode 100644 index 0000000000..b3f5173be3 --- /dev/null +++ b/2.5/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + Overview - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

+ +

Overview

+ +

Author +Latest Version +Total Downloads +Software License +Build Status +Coverage Status +Quality Score

+ +

The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

+ +

Installation

+ +

This library can be installed via Composer:

+ +
composer require league/commonmark
+
+ +

See the installation section for more details.

+ +

Basic Usage

+ +

Simply instantiate the converter and start converting some Markdown to HTML!

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter();
+echo $converter->convert('# Hello, World!')->getContent();
+
+// <h1>Hello, World!</h1>
+
+ +

+Important: See the basic usage and security sections for important details.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/installation/index.html b/2.5/installation/index.html new file mode 100644 index 0000000000..4fbd599f71 --- /dev/null +++ b/2.5/installation/index.html @@ -0,0 +1,424 @@ + + + + + + + + + + + + + + + + + Installation - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Installation

+ +

The recommended installation method is via Composer.

+ +
composer require "league/commonmark:^2.5"
+
+ +

Ensure that you’ve set up your project to autoload Composer-installed packages.

+ +

Versioning

+ +

SemVer will be followed closely. It’s highly recommended that you use Composer’s caret operator to ensure compatibility; for example: ^2.5. This is equivalent to >=2.5 <3.0.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/security/index.html b/2.5/security/index.html new file mode 100644 index 0000000000..ff4baae79c --- /dev/null +++ b/2.5/security/index.html @@ -0,0 +1,499 @@ + + + + + + + + + + + + + + + + + Security - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Security

+ +

In order to be fully compliant with the CommonMark spec, certain security settings are disabled by default. You will want to configure these settings if untrusted users will be providing the Markdown content:

+ +
    +
  • html_input: How to handle raw HTML
  • +
  • allow_unsafe_links: Whether unsafe links are permitted
  • +
  • max_nesting_level: Protected against long render times or segfaults
  • +
+ +

Further information about each option can be found below.

+ +

HTML Input

+ +

All HTML input is unescaped by default. This behavior ensures that league/commonmark is 100% compliant with the CommonMark spec.

+ +

If you’re developing an application which renders user-provided Markdown from potentially untrusted users, you are strongly encouraged to set the html_input option in your configuration to either escape or strip:

+ +

Example - Escape all raw HTML input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'escape']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// &lt;script&gt;alert("Hello XSS!");&lt;/script&gt;
+
+ +

Example - Strip all HTML from the input

+ +
use League\CommonMark\CommonMarkConverter;
+
+$converter = new CommonMarkConverter(['html_input' => 'strip']);
+echo $converter->convert('<script>alert("Hello XSS!");</script>');
+
+// (empty output)
+
+ +

Failing to set this option could make your site vulnerable to cross-site scripting (XSS) attacks!

+ +

See the configuration section for more information.

+ + + +

Unsafe links are also allowed by default due to CommonMark spec compliance. An unsafe link is one that uses any of these protocols:

+ +
    +
  • javascript:
  • +
  • vbscript:
  • +
  • file:
  • +
  • data: (except for data:image in png, gif, jpeg, or webp format)
  • +
+ +

To prevent these from being parsed and rendered, you should set the allow_unsafe_links option to false.

+ +

Nesting Level

+ +

No maximum nesting level is enforced by default. Markdown content which is too deeply-nested (like 10,000 nested blockquotes: ‘> > > > > …’) could result in long render times or segfaults.

+ +

If you need to parse untrusted input, consider setting a reasonable max_nesting_level (perhaps 10-50) depending on your needs. Once this nesting level is hit, any subsequent Markdown will be rendered as plain text.

+ +

Example - Prevent deep nesting

+ +
use League\CommonMark\CommonMarkConverter;
+
+$markdown = str_repeat('> ', 10000) . ' Foo';
+
+$converter = new CommonMarkConverter(['max_nesting_level' => 5]);
+echo $converter->convert($markdown);
+
+// <blockquote>
+//   <blockquote>
+//     <blockquote>
+//       <blockquote>
+//         <blockquote>
+//           <p>&gt; &gt; &gt; &gt; &gt; &gt; &gt; ... Foo</p></blockquote>
+//       </blockquote>
+//     </blockquote>
+//   </blockquote>
+// </blockquote>
+
+ +

See the configuration section for more information.

+ +

Additional Filtering

+ +

Although this library does offer these security features out-of-the-box, some users may opt to also run the HTML output through additional filtering layers (like HTMLPurifier). If you do this, make sure you thoroughly test your additional post-processing steps and configure them to work properly with the types of HTML elements and attributes that converted Markdown might produce, otherwise, you may end up with weird behavior like missing images, broken links, mismatched HTML tags, etc.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/support/index.html b/2.5/support/index.html new file mode 100644 index 0000000000..d09ab1e327 --- /dev/null +++ b/2.5/support/index.html @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + Support - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Support

+ +

Here are some useful resources to help you use this project:

+ + + +

Supported Versions

+ +

See our security policy for information about the support cycle for bug fixes and security updates.

+ +

Reporting a Vulnerability

+ +

If you discover a security vulnerability within this package, please use the Tidelift security contact form or email Colin O’Dell at colinodell@gmail.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced!

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/upgrading/index.html b/2.5/upgrading/index.html new file mode 100644 index 0000000000..b3e9688c27 --- /dev/null +++ b/2.5/upgrading/index.html @@ -0,0 +1,415 @@ + + + + + + + + + + + + + + + + + Upgrading from 2.4 to 2.5 - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

Upgrading from 2.4 to 2.5

+ +

These are no significant changes since 2.4.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/2.5/xml/index.html b/2.5/xml/index.html new file mode 100644 index 0000000000..ae70ebff2e --- /dev/null +++ b/2.5/xml/index.html @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + XML Rendering - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +

+ + +
+
+ + + + +
+ +
+

Versions

+ +
+ + + + + + + + + + + + +
+
+ + + + + + +

XML Rendering

+ +

Version 2.0 introduced the ability to render Markdown Document objects in XML. This is particularly useful for debugging custom extensions as you can see the XML representation of the Abstract Syntax Tree.

+ +

To convert Markdown to XML, you would instantiate a MarkdownToXmlConverter with an Environment and then call convert() on any Markdown.

+ +
use League\CommonMark\Environment\Environment;
+use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
+use League\CommonMark\Xml\MarkdownToXmlConverter;
+
+$environment = new Environment();
+$environment->addExtension(new CommonMarkCoreExtension());
+
+$converter = new MarkdownToXmlConverter($environment);
+
+echo $converter->convert('# **Hello** World!');
+
+ +

This will display XML output like this:

+ +
<?xml version="1.0" encoding="UTF-8"?>
+<document xmlns="http://commonmark.org/xml/1.0">
+    <heading level="1">
+        <strong>
+            <text>Hello</text>
+        </strong>
+        <text> World!</text>
+    </heading>
+</document>
+
+ +

Alternatively, if you already have a Document object you want to visualize in XML, you can use theXmlRenderer class to convert it to XML.

+ +

Return Value

+ +

Like with CommonMarkConverter::convert(), the renderDocument() actually returns an instance of League\CommonMark\Output\RenderedContentInterface. You can cast this (implicitly, as shown above, or explicitly) to a string or call getContent() to get the final XML output.

+ +

Customizing the XML Output

+ +

See the rendering documentation for information on customizing the XML output.

+ + +
+ +

+ Edit this page +

+
+ + +
+ + + + + + + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000000..e270d80a30 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +commonmark.thephpleague.com diff --git a/README.md b/README.md new file mode 100644 index 0000000000..9b7795d060 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ + +# Don't commit to this branch! + + +This branch is automatically generated by Jekyll via GitHub Actions. + +To make changes, please go back to the `main` branch and make changes within the `docs` folder. diff --git a/basic-usage/index.html b/basic-usage/index.html new file mode 100644 index 0000000000..7476744f89 --- /dev/null +++ b/basic-usage/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/changelog/index.html b/changelog/index.html new file mode 100644 index 0000000000..81d1283703 --- /dev/null +++ b/changelog/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/command-line/index.html b/command-line/index.html new file mode 100644 index 0000000000..69de29422d --- /dev/null +++ b/command-line/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/configuration/index.html b/configuration/index.html new file mode 100644 index 0000000000..b3c6c3ce4d --- /dev/null +++ b/configuration/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/custom.css b/custom.css new file mode 100644 index 0000000000..c44a81c06a --- /dev/null +++ b/custom.css @@ -0,0 +1,430 @@ +html { + background:#f2f7fc; +} + +body { + min-width: 320px; + display: flex; + flex-flow: column; +} + +blockquote { + border-left: 4px solid #ccc; + font-style:italic; + box-sizing: border-box; + padding:0 10px; +} + +header { + position:fixed; + height:4.5em; + z-index: 1; + border-bottom:1px solid #cfe4f9; + max-width: none; + width: 100%; + padding:0; + margin:0; + background:#fff; +} + +header .header-content:after { + content: ""; + display: table; + clear: both; +} + +header .title { + float: left; + text-align:left; + font-family: "Museo 100", sans-serif; + font-weight: bold; + margin:0; + padding:0; + width:400px; +} + +header .title a { + color:#ff4143; + text-decoration: none; +} + +header .search { + float:left; + text-align:right; + width:calc(100% - 480px); + padding-right:.3em; +} + +.banner { + max-width: 100%; +} + +#doc-search { + font-family: "Museo Sans 500", sans-serif; + font-weight: normal; + font-size: 16px; + border:1px solid #e8e8e8; + background-color:#f4f4f4; + padding:.5em 1em; + margin-left:.3em; + border-radius: .3em; +} + +header .versions { + float:left; + display:none; + font-family: "Museo Sans 500", sans-serif; + font-weight: normal; +} + +header .versions h2 { + font-family: "Museo Sans 500", sans-serif; + font-weight: normal; + font-size: 16px; + background:#1672ce; + color:#fff; + margin:0; + width: 75px; + text-align: center; + border-radius: .3em; + margin-bottom:.4em; + padding:.5em .3em; + cursor: pointer; + transition: 0.3s; +} + +header .versions h2:hover { + background: #0a64bf; +} + +header .versions ul { + display:none; + margin:0; + padding:0; + list-style:none; + width: 75px; +} + +header .versions .show { + display:block; +} + +header .versions li { + margin:0; + padding:0; + text-align: center; +} + +header .versions a { + display:block; + margin:0; + padding:.5em .3em; + text-decoration:none; + color:#1672ce; + background: #fff; + border:solid #c7c7c7; + border-width:0 1px 1px; +} + +header .versions a:hover { + background:#f1f1f1; +} + +header .versions li:first-of-type a { + border-top-width: 1px; + border-radius:.3em .3em 0 0; +} + +header .versions li:last-of-type a { + border-radius:0 0 .3em .3em; +} + +label[for=menu] { + position:fixed; + z-index: 2; + top:20px; + right:0; + display:inline-block; + box-sizing: border-box; + background:transparent; + color:#1672ce; + width:50px; + font-size:30px; + line-height: 1; + padding:0; + margin:0; +} + +label[for=menu]:hover { + background:transparent; + color:#1672ce; +} + +main { + padding-top:4em; + background:none; +} + +/* ---------- header automatic permalink -----------*/ + +.header-permalink { + text-decoration: none; + color:transparent; + font-size:.8em; + vertical-align: super; +} + +.header-permalink:hover, +h1:hover .header-permalink, +h2:hover .header-permalink, +h3:hover .header-permalink, +h4:hover .header-permalink, +h5:hover .header-permalink { + text-decoration: none; + color:#777; +} + +h4 { + font-variant: small-caps; + font-size:1em; +} + +main article p { + max-width: 840px; +} + +main article a { + color:#1672ce; +} + +main article p code, +main article li code, +main article div > code { + font-family: Consolas,Monaco,'Andale Mono',monospace; + font-size: 17px; + line-height: 100%; + color:#1672ce; + background: #fff; + border:none; +} + +main article p img { + box-sizing: border-box; + margin:.3em; + padding:0; + display: block; +} + +main article p a img { + box-sizing: border-box; + margin:0; + padding:0; + display:inline; +} + +main article hr { + border: 1px solid #d9e0e6; +} + +footer { + border-top:1px solid #cfe4f9; + background:#fff; + max-width: none; + text-align:center; + color:#a1a1a1; +} + +footer span a { + color:#007ec6; +} + +main menu .menu-section { + border-bottom:1px dashed #cfe4f9; + padding-bottom:1em; +} + +main menu .menu-section:last-of-type { + border-bottom:none; +} + +main menu h2 { + margin-top:1.5em; + padding-left:.7em; + color:#2b3d50; + font-size:1em; + font-weight: bold; + text-transform: none; + box-shadow: none; +} + +main menu ul li a { + border-radius:.1em; + font-size:1em; + margin:.2em; + padding:.6em; + color:#1672ce; +} + +main menu ul li a:hover { + color:#1672ce; + padding:.6em; + background:transparent; + text-decoration: underline; +} + +main menu ul li.selected { + background: none; +} + +main menu ul li.selected a { + color:#fff; + background:#1672ce; + padding:.6em; +} + +main menu ul li.selected a:hover { + text-decoration: none; +} + +pre { + border-width:0 0 0 4px; + background: #fff; + width: fit-content; + max-width: 100%; +} + +main article table { + width: initial; + max-width: 100%; +} + +table { + border:1px solid #eee; + background: #fff; + box-shadow:0 6px 6px 0 rgba(80, 88, 94, .24); +} + +table th, +table th > * { + color:#fff; + background: #1672ce; +} + +@media screen and (max-width: 420px) { + table { + display: flex; + flex-direction: column; + justify-content: space-between; + font-size: 0.9rem; + } +} + +@media screen and (max-width: 375px) { + main article table td { + padding: 2px 5px 2px 2px; + } + thead tr { + font-size: 0.8rem; + } +} + +header .logo .name { + font-size: 1.6rem; + line-height: 100%; + margin: 0 !important; +} + +@media screen and (min-width: 768px) { + header .logo .name { + font-size: 2.5rem; + } +} + +@media screen and (min-width: 1024px) { + header .logo .name { + font-size: 3rem; + } +} + +header .logo em { + color: #777; + font-style: normal; +} + +@media screen and (max-width: 549px) { + header { + padding: 25px 0 20px 10px; + } + + menu { + text-align: center; + } + + header .logo { + text-align: left; + } +} + +@media screen and (min-width: 550px) { + header { + background:#fff; + } + + header .header-content { + box-sizing: border-box; + padding:1em; + } + + header .versions { + display: block; + } + + label[for=menu] { + display:none; + } + + main { + background: none; + } + + main menu { + border-right: 1px dashed #cfe4f9; + } + + main menu .versions-small { + display:none; + } + + main article p, + main article li { + font-size:.9em; + } +} + +@media screen and (max-width: 750px) { + #doc-search { + display: none; + } +} + +@media screen and (min-width: 850px) { + main article { + max-width: none; + } +} + +h2 code { + font-size: 0.8em; +} + +.features ul { + list-style-type: none; +} + +:target:before { + content: ' '; + display: block; + padding-top: 100px; + margin-top: -100px; + visibility: hidden; +} diff --git a/custom.js b/custom.js new file mode 100644 index 0000000000..a84d855e95 --- /dev/null +++ b/custom.js @@ -0,0 +1,18 @@ +(() => { + + const uri = new URL(location.href); + + document.querySelector('header nav h2').addEventListener('click', function () { + this.parentNode.querySelector('ul').classList.toggle('show'); + }, false); + + document.querySelectorAll("main h2[id]").forEach((header) => { + uri.hash = header.id; + let link = document.createElement("a"); + link.className = "header-permalink"; + link.title = "Permalink"; + link.href = uri.toString(); + link.innerHTML = "¶"; + header.appendChild(link); + }); +})(); \ No newline at end of file diff --git a/customization/abstract-syntax-tree/index.html b/customization/abstract-syntax-tree/index.html new file mode 100644 index 0000000000..1274f5275c --- /dev/null +++ b/customization/abstract-syntax-tree/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/block-parsing/index.html b/customization/block-parsing/index.html new file mode 100644 index 0000000000..70a69aefa7 --- /dev/null +++ b/customization/block-parsing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/block-rendering/index.html b/customization/block-rendering/index.html new file mode 100644 index 0000000000..f266b31e1d --- /dev/null +++ b/customization/block-rendering/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/configuration/index.html b/customization/configuration/index.html new file mode 100644 index 0000000000..8a7ed07244 --- /dev/null +++ b/customization/configuration/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/cursor/index.html b/customization/cursor/index.html new file mode 100644 index 0000000000..804ea6f6b9 --- /dev/null +++ b/customization/cursor/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/delimiter-processing/index.html b/customization/delimiter-processing/index.html new file mode 100644 index 0000000000..91fdca3242 --- /dev/null +++ b/customization/delimiter-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/disabling-features/index.html b/customization/disabling-features/index.html new file mode 100644 index 0000000000..26e5f85a07 --- /dev/null +++ b/customization/disabling-features/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/document-processing/index.html b/customization/document-processing/index.html new file mode 100644 index 0000000000..07c45afabb --- /dev/null +++ b/customization/document-processing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/environment/index.html b/customization/environment/index.html new file mode 100644 index 0000000000..da65b028c5 --- /dev/null +++ b/customization/environment/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/event-dispatcher/index.html b/customization/event-dispatcher/index.html new file mode 100644 index 0000000000..07c45afabb --- /dev/null +++ b/customization/event-dispatcher/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/extensions/index.html b/customization/extensions/index.html new file mode 100644 index 0000000000..d2f3151b4e --- /dev/null +++ b/customization/extensions/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/index.html b/customization/index.html new file mode 100644 index 0000000000..e4e65875c6 --- /dev/null +++ b/customization/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/inline-parsing/index.html b/customization/inline-parsing/index.html new file mode 100644 index 0000000000..88d4c036e5 --- /dev/null +++ b/customization/inline-parsing/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/inline-rendering/index.html b/customization/inline-rendering/index.html new file mode 100644 index 0000000000..f266b31e1d --- /dev/null +++ b/customization/inline-rendering/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/overview/index.html b/customization/overview/index.html new file mode 100644 index 0000000000..e4e65875c6 --- /dev/null +++ b/customization/overview/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/customization/slug-normalizer/index.html b/customization/slug-normalizer/index.html new file mode 100644 index 0000000000..7c2c0bade6 --- /dev/null +++ b/customization/slug-normalizer/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/attributes/index.html b/extensions/attributes/index.html new file mode 100644 index 0000000000..8a19c4c27f --- /dev/null +++ b/extensions/attributes/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/autolinks/index.html b/extensions/autolinks/index.html new file mode 100644 index 0000000000..40ee2c88fa --- /dev/null +++ b/extensions/autolinks/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/commonmark/index.html b/extensions/commonmark/index.html new file mode 100644 index 0000000000..d22c206312 --- /dev/null +++ b/extensions/commonmark/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/default-attributes/index.html b/extensions/default-attributes/index.html new file mode 100644 index 0000000000..7e55da36e7 --- /dev/null +++ b/extensions/default-attributes/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/description-lists/index.html b/extensions/description-lists/index.html new file mode 100644 index 0000000000..71d5171eb6 --- /dev/null +++ b/extensions/description-lists/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/disallowed-raw-html/index.html b/extensions/disallowed-raw-html/index.html new file mode 100644 index 0000000000..f42d4a3bbd --- /dev/null +++ b/extensions/disallowed-raw-html/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/embed/index.html b/extensions/embed/index.html new file mode 100644 index 0000000000..11ae010e04 --- /dev/null +++ b/extensions/embed/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/external-links/index.html b/extensions/external-links/index.html new file mode 100644 index 0000000000..1306e829b1 --- /dev/null +++ b/extensions/external-links/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/footnotes/index.html b/extensions/footnotes/index.html new file mode 100644 index 0000000000..068ac387e7 --- /dev/null +++ b/extensions/footnotes/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/front-matter/index.html b/extensions/front-matter/index.html new file mode 100644 index 0000000000..09a3fd0161 --- /dev/null +++ b/extensions/front-matter/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/github-flavored-markdown/index.html b/extensions/github-flavored-markdown/index.html new file mode 100644 index 0000000000..2485fab767 --- /dev/null +++ b/extensions/github-flavored-markdown/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/heading-permalinks/index.html b/extensions/heading-permalinks/index.html new file mode 100644 index 0000000000..51174b99db --- /dev/null +++ b/extensions/heading-permalinks/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/index.html b/extensions/index.html new file mode 100644 index 0000000000..ee7b54abc7 --- /dev/null +++ b/extensions/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/inlines-only/index.html b/extensions/inlines-only/index.html new file mode 100644 index 0000000000..c7650c664a --- /dev/null +++ b/extensions/inlines-only/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/mentions/index.html b/extensions/mentions/index.html new file mode 100644 index 0000000000..2e73b7b17f --- /dev/null +++ b/extensions/mentions/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/overview/index.html b/extensions/overview/index.html new file mode 100644 index 0000000000..ee7b54abc7 --- /dev/null +++ b/extensions/overview/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/smart-punctuation/index.html b/extensions/smart-punctuation/index.html new file mode 100644 index 0000000000..5dc243479c --- /dev/null +++ b/extensions/smart-punctuation/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/strikethrough/index.html b/extensions/strikethrough/index.html new file mode 100644 index 0000000000..550372e601 --- /dev/null +++ b/extensions/strikethrough/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/table-of-contents/index.html b/extensions/table-of-contents/index.html new file mode 100644 index 0000000000..20e9e35257 --- /dev/null +++ b/extensions/table-of-contents/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/tables/index.html b/extensions/tables/index.html new file mode 100644 index 0000000000..628ba85885 --- /dev/null +++ b/extensions/tables/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/extensions/task-lists/index.html b/extensions/task-lists/index.html new file mode 100644 index 0000000000..5f0011f61d --- /dev/null +++ b/extensions/task-lists/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
+
+

league/commonmark

+

Markdown done right

+

$ composer require league/commonmark

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+ + + + diff --git a/global.css b/global.css new file mode 100644 index 0000000000..984a5017f2 --- /dev/null +++ b/global.css @@ -0,0 +1,42 @@ +.btn { + padding: .5rem 1rem; + margin-right: .25rem; + text-align: center; + border-radius: .25rem; + color: inherit; + text-decoration: inherit; + transition: 0.3s; +} + +.btn-block { + display: block; + width: auto; +} + +.btn-sm { + padding: .3rem .6rem; + font-size: .8rem; +} + +.btn-black { + background-color: #222222; + color: #ffffff; +} +.btn-black:hover { + background-color: #333; + color: #ffffff; +} + +.btn-pink { + background-color: #d53f8c; +} +.btn-pink:hover { + background-color: #d62b83; +} + +.btn-green { + background-color: #38a169; +} +.btn-green:hover { + background-color: #28a160; +} diff --git a/homepage.css b/homepage.css new file mode 100644 index 0000000000..cb7fc12c0e --- /dev/null +++ b/homepage.css @@ -0,0 +1,381 @@ +blockquote { + border-left: 4px solid #ccc; + font-style:italic; + box-sizing: border-box; + padding:0 10px; +} + +footer { + text-align: center; + color:#fff; +} + +pre { + border-width:0 0 0 4px; + background: #fff; +} + +main p code, +main li code, +main div > code { + font-family: Consolas,Monaco,'Andale Mono',monospace; + font-size: 17px; + line-height: 100%; + color:#1672ce; + background: #fff; + border:none; +} +/* -------- homepage css -----------------*/ + +.homepage header { + margin:0 auto; + padding:0 0 3em; + box-sizing: border-box; + background:#19242F url(//thephpleague.com/img/header_bg.png) no-repeat top center; + background-size: cover; + max-width: none; + color:#fff; + font-family: "Museo 300", sans-serif; +} + +.homepage header h1 { + margin:.3em auto; + font-family: "Museo 300", sans-serif; + line-height: 250%; + font-weight: normal; +} + +.homepage header h2 { + margin:0 auto; + font-family: "Museo 300", sans-serif; + color:#ff4043; + font-size:36px; + line-height:1.33; + font-weight:normal; +} + +.homepage .badge-list { + background: #fff; + padding: 3em 0 0 0; + text-align: center; +} + +.homepage .composer span { + padding:.3em 1em; + background-color:rgba(0, 0, 0, .3); + color:#fff; + font-size:.9rem; + font-family: Consolas, Monaco ,'Andale Mono', monospace; + line-height: 140%; + text-align: left; + white-space: pre; + word-wrap: normal; + word-spacing: normal; + hyphens: none; + display:inline-block; + border-radius: .3em; +} + +.homepage .hot-links { + margin:0 auto; + padding:0; + list-style:none; + width:300px; + display:flex; + align-content: top; + align-items: center; +} + +.homepage .hot-links li { + padding:0; + margin:0; +} + +.homepage .hot-links a { + display:block; + margin:0; + padding:1em; + width:150px; + border-radius:2px 0 0 2px; + text-decoration:none; + text-align:center; + font-weight:bold; + background:#fff; + color:#ff4043; +} + +.homepage .hot-links :last-child a { + border-radius:0 2px 2px 0; + background:#ff4043; + color:#fff; +} + +.homepage .hot-links:hover :last-child a { + color:#ff4043; +} + +.homepage .hot-links:hover a { + background:#fff; + color:#ff4043; +} + +.homepage .hot-links:hover a:hover { + background: #ff4043; + color: #fff; +} + +.homepage main { + color: #2b3d50; + font-family: "Museo 300", sans-serif; + line-height: 160%; + font-weight: normal; + background:#19242f; + width:auto; + right:auto; +} + +.homepage main > div { + margin:0 auto; + padding:3em 0; + box-sizing: border-box; +} + +.inner-content { + margin:0 auto; + padding:1em; + box-sizing: border-box; + text-align:center; +} + +.inner-content h1 { + color:#fff; + font-size:50px; + line-height:100%; + font-weight:normal; + font-family:"Museo Sans 300", sans-serif; + text-transform:uppercase; + margin:0; +} + +.inner-content:after { + content: ''; + display:table; + clear:both; +} + +.projects, +.sponsors { + color: #fff; +} + +.projects h2, +.sponsors h2 { + font-size:50px; + line-height:100%; + font-weight:normal; + font-family:"Museo Sans 300", sans-serif; + text-transform:uppercase; + margin: 0 0 1em 0; +} + +.project-logos { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; +} + +.project-logos a { + display: block; + object-fit: contain; + margin: 0 16px; +} + +.project-logos img { + height: 70px; + max-width: 180px; +} + +.sponsors a { + color: #3399ff; +} +.sponsors ul { + list-style: none; + font-size: 1.1em; +} + +.features { + background: #F2F7FC; + border-bottom:1px solid #CFE4F9; + color:#666; + line-height: 1.5; + font-weight: normal; + font-variant: normal; + font-style: normal; + font-size: 18px; + font-family: "Museo 300", sans-serif; +} + +.features h1 { + color:#6abcdd; +} + +.features h2 { + color:#6abcdd; + font-weight: normal; +} + +.features p { + text-align: center; +} + +.features a { + color:#6abcdd; +} + +.highlights { + background:#fff; + border-bottom:1px solid #CFE4F9; +} + +.highlights .description { + color:#666; + line-height: 1.5; + font-weight: normal; + font-variant: normal; + font-style: normal; + font-size: 18px; + font-family: "Museo 300", sans-serif; + text-align: left; +} + +.highlights h1 { + color:#ff4043; + font-size: 36px; + line-height:115%; +} + +.highlights ol { + margin:0; + text-align:left; +} + +.highlights li { + margin:0 0 15px 0; + color:#666; + font-size:16px; + font-weight:bold; +} + +.highlights li p { + margin:0; + line-height: 1.4; + font-weight: normal; + font-variant: normal; + font-style: normal; + font-size: 18px; + font-family: "Museo 300", sans-serif; + color:#ff4043; +} + +.highlights a { + color:#ff4043; +} + +.questions { + padding:0; + background:#fff; + color:#666; + border-bottom:1px solid #CFE4F9; +} + +.questions h1 { + font-size:36px; + color:#ff4043; +} + +.questions a { + color:#6abcdd; +} + +.questions a:hover { + text-decoration: none; +} + +@media screen and (max-width: 549px) { + .homepage header h1 { + margin:0 auto; + font-size:clamp(1.8rem, 5.9vw, 42px); + } + + .homepage .composer span { + font-size:1rem; + } +} + +@media screen and (min-width: 549px) { + .homepage header h1 { + margin:0 auto; + font-size:clamp(1.8rem, 5.9vw, 96px); + } + + .inner-content { + max-width:1000px; + } + + .homepage main { + width:initial; + right:initial; + } + + .highlights h1 { + font-size: 50px; + } + + .highlights li { + font-size:24px; + } + + .highlights li p { + font-size:20px; + } + + .highlights .column { + float:left; + width:45%; + } + + .highlights .one { + margin:0 10% 0 0; + } + + .documentation h1 { + font-size:50px; + } +} + +@media screen and (min-width:700px) { + .documentation .version { + float:left; + width:42%; + margin:2%; + max-width:289px; + } +} + +@media screen and (min-width: 910px) { + .documentation .version { + float:left; + width:30%; + margin:1%; + } + + .homepage header h1 { + margin:0 auto; + font-size:clamp(1.8rem, 5.9vw, 96px); + } +} + +#sponsors + p + ul { + list-style-type: none; +} diff --git a/images/commonmark-banner.png b/images/commonmark-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..5ccbd149ffa768c52496847593b9430d93d72e25 GIT binary patch literal 124118 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ξ03j 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 zG4y7p7Y#j0dOSM$0RR91000000000000000000000000000000000000000000030 Z!x#TB6_oxaK=A+o002ovPDHLkV1hFFguws+ literal 0 HcmV?d00001 diff --git a/images/commonmark-social.png b/images/commonmark-social.png new file mode 100644 index 0000000000000000000000000000000000000000..9d8d21040e061c7cb33618a9f10820e295195123 GIT binary patch literal 310396 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�(qSfY2KgkcgO5m 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%0Ai&kuM*3jc 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

  • q?d)F#s4Bv-}6xKAjdiW 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 phL;zxvxsnELlxUitm6 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

    -!9D5OUR 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

    %Z^K+fji6`%X3yx(Q~`WF}JW5D0$*EfyK0yn6k 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 + + + Cachet + Created with Sketch. + + + + + + + + + + + diff --git a/images/users/daux.png b/images/users/daux.png new file mode 100644 index 0000000000000000000000000000000000000000..42979d4b2eb28ca9abc334b3812e485f03e4ce3c GIT binary patch literal 4282 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 81 \ No newline at end of file diff --git a/images/users/laravel.svg b/images/users/laravel.svg new file mode 100644 index 0000000000..ebe99bb2fc --- /dev/null +++ b/images/users/laravel.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + diff --git a/images/users/neos.svg b/images/users/neos.svg new file mode 100644 index 0000000000..98e790191e --- /dev/null +++ b/images/users/neos.svg @@ -0,0 +1 @@ +neos_avatar_primary \ No newline at end of file diff --git a/images/users/twig.svg b/images/users/twig.svg new file mode 100644 index 0000000000..5d977dc426 --- /dev/null +++ b/images/users/twig.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000000..9589229438 --- /dev/null +++ b/index.html @@ -0,0 +1,218 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + + + + + + + + + + +

    + +
    + +
    +
    + Author + Latest Version + Total Downloads + Software License + Build Status + Coverage Status + Quality Score +
    +
    +
    +
    +

    Highlights

    +
    +

    The PHP CommonMark parser is a robust, highly-extensible Markdown parser for PHP based on the CommonMark and GitHub-Flavored Markdown specifications.

    +
    +
    +
    +
      + +
    1. The only PHP library fully compatible with the CommonMark and GitHub-Flavored Markdown specs

    2. + +
    3. Highly extensible architecture supports custom syntax and other customizations

    4. + +
    5. Community extensions and integrations for popular frameworks and CMSes

    6. + +
    7. Easy-to-use API

    8. + +
    +
    +
    +
    + +
    +
    +

    Used By

    + +
    +
    + +
    +
    + + + + +

    Features

    + +

    Easy Usage

    + +
    use League\CommonMark\CommonMarkConverter;
    +
    +$converter = new CommonMarkConverter();
    +echo $converter->convert('# Hello World!');
    +
    +// <h1>Hello World!</h1>
    +
    + +

    Security

    + +

    All CommonMark features are supported by default, including raw HTML and unsafe links, which you may want to disable using the html_input and allow_unsafe_links options:

    + +
    use League\CommonMark\CommonMarkConverter;
    +
    +$converter = new CommonMarkConverter(['html_input' => 'escape', 'allow_unsafe_links' => false]);
    +echo $converter->convert('# Hello World!');
    +
    +// <h1>Hello World!</h1>
    +
    + +

    Included Extensions

    + +

    This project includes several built-in extensions you can use to enable additional features and syntax.

    + +

    Customization

    + +

    This library allows you to add custom syntax, renderers, and more. Check out the Customization section for more information.

    + +

    Community Integrations & Extensions

    + +

    An updated list of pre-built integrations and extensions can be found in the Related Packages section of the README.

    + +
    +
    + +
    +
    +

    Sponsors

    +

    We'd like to extend our sincere thanks the following sponsors who support ongoing development of this project:

    + +

    + Are you interested in sponsoring development of this project?
    + See https://www.colinodell.com/sponsor for a list of ways to contribute! +

    +
    +
    + +
    +
    +

    Questions?

    +

    league/commonmark was created by Colin O'Dell. Find him on Twitter at @colinodell.

    +
    +
    + + +
    + + + + + + diff --git a/installation/index.html b/installation/index.html new file mode 100644 index 0000000000..272dd533c0 --- /dev/null +++ b/installation/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
    +
    +

    league/commonmark

    +

    Markdown done right

    +

    $ composer require league/commonmark

    +
    +
    + +
    +
    +
    +

    Redirecting…

    +
    + Click here if you are not redirected. + +
    +
    +
    +
    + + + + diff --git a/redirects.json b/redirects.json new file mode 100644 index 0000000000..21576bac81 --- /dev/null +++ b/redirects.json @@ -0,0 +1 @@ +{"/0.20/customization/abstract-syntax-tree/":"http://commonmark.thephpleague.com/1.0/customization/abstract-syntax-tree/","/customization/abstract-syntax-tree/":"http://commonmark.thephpleague.com/2.5/customization/abstract-syntax-tree/","/extensions/attributes/":"http://commonmark.thephpleague.com/2.4/extensions/attributes/","/extensions/autolinks/":"http://commonmark.thephpleague.com/2.4/extensions/autolinks/","/basic-usage/":"http://commonmark.thephpleague.com/2.5/basic-usage/","/0.20/basic-usage/":"http://commonmark.thephpleague.com/1.0/basic-usage/","/customization/block-parsing/":"http://commonmark.thephpleague.com/2.4/customization/block-parsing/","/0.20/customization/block-parsing/":"http://commonmark.thephpleague.com/1.0/customization/block-parsing/","/0.20/customization/block-rendering/":"http://commonmark.thephpleague.com/1.0/customization/block-rendering/","/changelog/":"http://commonmark.thephpleague.com/releases/","/0.20/changelog/":"http://commonmark.thephpleague.com/1.0/changelog/","/0.20/command-line/":"http://commonmark.thephpleague.com/1.0/command-line/","/command-line/":"http://commonmark.thephpleague.com/1.6/command-line/","/extensions/commonmark/":"http://commonmark.thephpleague.com/2.5/extensions/commonmark/","/configuration/":"http://commonmark.thephpleague.com/2.4/configuration/","/customization/configuration/":"http://commonmark.thephpleague.com/2.4/customization/configuration/","/0.20/configuration/":"http://commonmark.thephpleague.com/1.0/configuration/","/0.20/customization/cursor/":"http://commonmark.thephpleague.com/1.0/customization/cursor/","/customization/cursor/":"http://commonmark.thephpleague.com/2.4/customization/cursor/","/extensions/default-attributes/":"http://commonmark.thephpleague.com/2.5/extensions/default-attributes/","/customization/delimiter-processing/":"http://commonmark.thephpleague.com/2.5/customization/delimiter-processing/","/0.20/customization/delimiter-processing/":"http://commonmark.thephpleague.com/1.0/customization/delimiter-processing/","/extensions/description-lists/":"http://commonmark.thephpleague.com/2.4/extensions/description-lists/","/customization/disabling-features/":"http://commonmark.thephpleague.com/2.4/customization/disabling-features/","/extensions/disallowed-raw-html/":"http://commonmark.thephpleague.com/2.5/extensions/disallowed-raw-html/","/extensions/embed/":"http://commonmark.thephpleague.com/2.5/extensions/embed/","/0.20/customization/environment/":"http://commonmark.thephpleague.com/1.0/customization/environment/","/customization/environment/":"http://commonmark.thephpleague.com/2.5/customization/environment/","/1.5/customization/document-processing/":"http://commonmark.thephpleague.com/1.5/customization/event-dispatcher/","/1.4/customization/document-processing/":"http://commonmark.thephpleague.com/1.4/customization/event-dispatcher/","/customization/document-processing/":"http://commonmark.thephpleague.com/2.4/customization/event-dispatcher/","/customization/event-dispatcher/":"http://commonmark.thephpleague.com/2.4/customization/event-dispatcher/","/0.20/customization/document-processing/":"http://commonmark.thephpleague.com/1.0/customization/event-dispatcher/","/1.0/customization/document-processing/":"http://commonmark.thephpleague.com/1.0/customization/event-dispatcher/","/1.3/customization/document-processing/":"http://commonmark.thephpleague.com/1.3/customization/event-dispatcher/","/customization/extensions/":"http://commonmark.thephpleague.com/2.5/customization/extensions/","/0.20/customization/extensions/":"http://commonmark.thephpleague.com/1.0/customization/extensions/","/extensions/external-links/":"http://commonmark.thephpleague.com/2.5/extensions/external-links/","/extensions/footnotes/":"http://commonmark.thephpleague.com/2.4/extensions/footnotes/","/extensions/front-matter/":"http://commonmark.thephpleague.com/2.5/extensions/front-matter/","/extensions/github-flavored-markdown/":"http://commonmark.thephpleague.com/2.5/extensions/github-flavored-markdown/","/extensions/heading-permalinks/":"http://commonmark.thephpleague.com/2.5/extensions/heading-permalinks/","/0.20/":"http://commonmark.thephpleague.com/1.0/","/customization/inline-parsing/":"http://commonmark.thephpleague.com/2.5/customization/inline-parsing/","/0.20/customization/inline-parsing/":"http://commonmark.thephpleague.com/1.0/customization/inline-parsing/","/0.20/customization/inline-rendering/":"http://commonmark.thephpleague.com/1.0/customization/inline-rendering/","/extensions/inlines-only/":"http://commonmark.thephpleague.com/2.4/extensions/inlines-only/","/installation/":"http://commonmark.thephpleague.com/2.4/installation/","/0.20/installation/":"http://commonmark.thephpleague.com/1.0/installation/","/extensions/mentions/":"http://commonmark.thephpleague.com/2.4/extensions/mentions/","/customization/":"http://commonmark.thephpleague.com/2.5/customization/overview/","/customization/overview/":"http://commonmark.thephpleague.com/2.5/customization/overview/","/extensions/":"http://commonmark.thephpleague.com/2.5/extensions/overview/","/extensions/overview/":"http://commonmark.thephpleague.com/2.5/extensions/overview/","/0.20/customization/":"http://commonmark.thephpleague.com/1.0/customization/overview/","/0.20/customization/overview/":"http://commonmark.thephpleague.com/1.0/customization/overview/","/upgrading/":"http://commonmark.thephpleague.com/2.5/upgrading/","/upgrading/changelog/":"http://commonmark.thephpleague.com/releases/","/customization/block-rendering/":"http://commonmark.thephpleague.com/2.5/customization/rendering/","/customization/inline-rendering/":"http://commonmark.thephpleague.com/2.5/customization/rendering/","/security/":"http://commonmark.thephpleague.com/2.5/security/","/0.20/security/":"http://commonmark.thephpleague.com/1.0/security/","/customization/slug-normalizer/":"http://commonmark.thephpleague.com/2.5/customization/slug-normalizer/","/extensions/smart-punctuation/":"http://commonmark.thephpleague.com/2.5/extensions/smart-punctuation/","/extensions/strikethrough/":"http://commonmark.thephpleague.com/2.5/extensions/strikethrough/","/support/":"http://commonmark.thephpleague.com/2.5/support/","/extensions/table-of-contents/":"http://commonmark.thephpleague.com/2.5/extensions/table-of-contents/","/extensions/tables/":"http://commonmark.thephpleague.com/2.5/extensions/tables/","/extensions/task-lists/":"http://commonmark.thephpleague.com/2.5/extensions/task-lists/","/0.20/upgrading/":"http://commonmark.thephpleague.com/1.0/upgrading/","/xml/":"http://commonmark.thephpleague.com/2.5/xml/"} \ No newline at end of file diff --git a/releases/index.html b/releases/index.html new file mode 100644 index 0000000000..f5b23f5e4c --- /dev/null +++ b/releases/index.html @@ -0,0 +1,2902 @@ + + + + + + + + + + + + + + + + + Release Notes - CommonMark for PHP + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +

    + +

    + + +
    +
    + + + + +
    + +
    +

    Versions

    + +
    + + + +

    Upgrading Guide

    + + + +
    +
    + + + + + + +

    Release Notes

    + +

    2.5.3 - 2024-08-16

    + +

    Changed

    + +
      +
    • Made compatible with CommonMark spec 0.31.1, including: +
        +
      • Remove source, add search to list of recognized block tags
      • +
      +
    • +
    + +

    Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.2…2.5.3

    + +

    2.5.2 - 2024-08-14

    + +

    Changed

    + +
      +
    • Boolean attributes now require an explicit true value (#1040)
    • +
    + +

    Fixed

    + +
      +
    • Fixed regression where text could be misinterpreted as an attribute (#1040)
    • +
    + +
    + +

    Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.1…2.5.2

    + +

    2.5.1 - 2024-07-24

    + +

    Fixed

    + +
      +
    • Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
    • +
    • Fixed incorrect Table start line numbers (#1037)
    • +
    + +

    New Contributors

    +
      +
    • @jasonvarga made their first contribution in https://github.com/thephpleague/commonmark/pull/1035
    • +
    + +

    Full Changelog: https://github.com/thephpleague/commonmark/compare/2.5.0…2.5.1

    + +

    2.5.0 - 2024-07-22

    + +

    Added

    + +
      +
    • The AttributesExtension now supports attributes without values (#985, #986)
    • +
    • The AutolinkExtension exposes two new configuration options to override the default behavior (#969, #987): +
        +
      • autolink/allowed_protocols - an array of protocols to allow autolinking for
      • +
      • autolink/default_protocol - the default protocol to use when none is specified
      • +
      +
    • +
    + +

    Changed

    + +
      +
    • Made compatible with CommonMark spec 0.31.0, including: +
        +
      • Allow closing fence to be followed by tabs
      • +
      • Remove restrictive limitation on inline comments
      • +
      • Unicode symbols now treated like punctuation (for purposes of flankingness)
      • +
      • Trailing tabs on the last line of indented code blocks will be excluded
      • +
      • Improved HTML comment matching
      • +
      +
    • +
    • Paragraphs only containing link reference definitions will be kept in the AST until the Document is finalized +
        +
      • (These were previously removed immediately after parsing the Paragraph)
      • +
      +
    • +
    + +

    Fixed

    + +
      +
    • Fixed list tightness not being determined properly in some edge cases
    • +
    • Fixed incorrect ending line numbers for several block types in various scenarios
    • +
    • Fixed lowercase inline HTML declarations not being accepted
    • +
    + +

    New Contributors

    +
      +
    • @svenluijten made their first contribution in https://github.com/thephpleague/commonmark/pull/986
    • +
    + +

    Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.4…2.5.0

    + +

    2.4.4 - 2024-07-22

    + +

    Fixed

    + +
      +
    • Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
    • +
    + +

    Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.3…2.4.4

    + +

    2.4.3 - 2024-07-22

    + +

    Fixed

    + +
      +
    • Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
    • +
    • Fixed UrlAutolinkParser incorrectly parsing text containing www anywhere before an autolink (#1025)
    • +
    + +

    New Contributors

    +
      +
    • @nfreader made their first contribution in https://github.com/thephpleague/commonmark/pull/1016
    • +
    • @macbookandrew made their first contribution in https://github.com/thephpleague/commonmark/pull/1025
    • +
    • @xavierlacot made their first contribution in https://github.com/thephpleague/commonmark/pull/1013
    • +
    + +

    Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.2…2.4.3

    + +

    2.4.2 - 2024-02-02

    + +

    Fixed

    + +
      +
    • Fixed declaration parser being too strict
    • +
    • FencedCodeRenderer: don’t add language- to class if already prefixed
    • +
    + +

    New Contributors

    +
      +
    • @sergiy-petrov made their first contribution in https://github.com/thephpleague/commonmark/pull/997
    • +
    • @clustermin made their first contribution in https://github.com/thephpleague/commonmark/pull/988
    • +
    • @peter279k made their first contribution in https://github.com/thephpleague/commonmark/pull/996
    • +
    + +

    Full Changelog: https://github.com/thephpleague/commonmark/compare/2.4.1…2.4.2

    + +

    2.4.1 - 2023-08-30

    + +

    Fixed

    + +
      +
    • Fixed ExternalLinkProcessor not fully disabling the rel attribute when configured to do so (#992)
    • +
    + +

    2.4.0 - 2023-03-24

    + +

    See the upgrading guide for more information about the exception-related changes

    + +

    Added

    + +
      +
    • Added generic CommonMarkException marker interface for all exceptions thrown by the library
    • +
    • Added several new specific exception types implementing that marker interface: +
        +
      • AlreadyInitializedException
      • +
      • InvalidArgumentException
      • +
      • IOException
      • +
      • LogicException
      • +
      • MissingDependencyException
      • +
      • NoMatchingRendererException
      • +
      • ParserLogicException
      • +
      +
    • +
    • Added more configuration options to the Heading Permalinks extension (#939): +
        +
      • heading_permalink/apply_id_to_heading - When true, the id attribute will be applied to the heading element itself instead of the <a> tag
      • +
      • heading_permalink/heading_class - class to apply to the heading element
      • +
      • heading_permalink/insert - now accepts none to prevent the creation of the <a> link
      • +
      +
    • +
    • Added new table/alignment_attributes configuration option to control how table cell alignment is rendered (#959)
    • +
    + +

    Changed

    + +
      +
    • Change several thrown exceptions from RuntimeException to LogicException (or something extending it), including: +
        +
      • CallbackGenerators that fail to set a URL or return an expected value
      • +
      • MarkdownParser when deactivating the last block parser or attempting to get an active block parser when they’ve all been closed
      • +
      • Adding items to an already-initialized Environment
      • +
      • Rendering a Node when no renderer has been registered for it
      • +
      +
    • +
    • HeadingPermalinkProcessor now throws InvalidConfigurationException instead of RuntimeException when invalid config values are given.
    • +
    • HtmlElement::setAttribute() no longer requires the second parameter for boolean attributes
    • +
    • Several small micro-optimizations
    • +
    • Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
    • +
    + +

    Fixed

    + +
      +
    • Fixed inaccurate @throws docblocks throughout the codebase, including ConverterInterface, MarkdownConverter, and MarkdownConverterInterface. +
        +
      • These previously suggested that only \RuntimeExceptions were thrown, which was inaccurate as \LogicExceptions were also possible.
      • +
      +
    • +
    + +

    2.3.9 - 2023-02-15

    + +

    Fixed

    + +
      +
    • Fixed autolink extension not detecting some URIs with underscores (#956)
    • +
    + +

    2.3.8 - 2022-12-10

    + +

    Fixed

    + +
      +
    • Fixed parsing issues when mb_internal_encoding() is set to something other than UTF-8 (#951)
    • +
    + +

    2.3.7 - 2022-11-17

    + +

    Fixed

    + +
      +
    • Fixed TaskListItemMarkerRenderer not including HTML attributes set on the node by other extensions (#947)
    • +
    + +

    2.3.6 - 2022-10-30

    + +

    Fixed

    + +
      +
    • Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a .) (#943)
    • +
    + +

    2.3.5 - 2022-07-29

    + +

    Fixed

    + +
      +
    • Fixed error using InlineParserEngine when no inline parsers are registered in the Environment (#908)
    • +
    + +

    2.3.4 - 2022-07-17

    + +

    Changed

    + +
      +
    • Made a number of small tweaks to the embed extension’s parsing behavior to fix #898: +
        +
      • Changed EmbedStartParser to always capture embed-like lines in container blocks, regardless of parent block type
      • +
      • Changed EmbedProcessor to also remove Embed blocks that aren’t direct children of the Document
      • +
      • Increased the priority of EmbedProcessor to 1010
      • +
      +
    • +
    + +

    Fixed

    + +
      +
    • Fixed EmbedExtension not parsing embeds following a list block (#898)
    • +
    + +

    2.3.3 - 2022-06-07

    + +

    Fixed

    + +
      +
    • Fixed DomainFilteringAdapter not reindexing the embed list (#884, #885)
    • +
    + +

    2.3.2 - 2022-06-03

    + +

    Fixed

    + +
      +
    • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
    • +
    + +

    2.2.5 - 2022-06-03

    + +

    Fixed

    + +
      +
    • Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
    • +
    + +

    2.3.1 - 2022-05-14

    + +

    Fixed

    + +
      +
    • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
    • +
    + +

    2.2.4 - 2022-05-14

    + +

    Fixed

    + +
      +
    • Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
    • +
    + +

    2.3.0 - 2022-04-07

    + +

    Added

    + +
      +
    • Added new EmbedExtension (#805)
    • +
    • Added DocumentRendererInterface as a replacement for the now-deprecated MarkdownRendererInterface
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated MarkdownRendererInterface; use DocumentRendererInterface instead
    • +
    + +

    2.2.3 - 2022-02-26

    + +

    Fixed

    + +
      +
    • Fixed front matter parsing with Windows line endings (#821)
    • +
    + +

    2.1.3 - 2022-02-26

    + +

    Fixed

    + +
      +
    • Fixed front matter parsing with Windows line endings (#821)
    • +
    + +

    2.0.4 - 2022-02-26

    + +

    Fixed

    + +
      +
    • Fixed front matter parsing with Windows line endings (#821)
    • +
    + +

    2.2.2 - 2022-02-13

    + +

    Fixed

    + +
      +
    • Fixed double-escaping of image alt text (#806, #810)
    • +
    • Fixed Psalm typehints for event class names
    • +
    + +

    2.1.2 - 2022-02-13

    + +

    Fixed

    + +
      +
    • Fixed double-escaping of image alt text (#806, #810)
    • +
    • Fixed Psalm typehints for event class names
    • +
    + +

    2.0.3 - 2022-02-13

    + +

    Fixed

    + +
      +
    • Fixed double-escaping of image alt text (#806, #810)
    • +
    • Fixed Psalm typehints for event class names
    • +
    + +

    2.2.1 - 2022-01-25

    + +

    Fixed

    + +
      +
    • Fixed symfony/deprecation-contracts constraint
    • +
    + +

    Removed

    + +
      +
    • Removed deprecation trigger from MarkdownConverterInterface to reduce noise
    • +
    + +

    2.2.0 - 2022-01-22

    + +

    Added

    + +
      +
    • Added new ConverterInterface
    • +
    • Added new MarkdownToXmlConverter class
    • +
    • Added new HtmlDecorator class which can wrap existing renderers with additional HTML tags
    • +
    • Added new table/wrap config to apply an optional wrapping/container element around a table (#780)
    • +
    + +

    Changed

    + +
      +
    • HtmlElement contents can now consist of any Stringable, not just HtmlElement and string
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated MarkdownConverterInterface and its convertToHtml() method; use ConverterInterface and convert() instead
    • +
    + +

    1.6.7 - 2022-01-13

    + +

    Changed

    + +
      +
    • Added ReturnTypeWillChange attribute to prevent PHP 8.1 deprecation warnings (#785)
    • +
    • Coerced punctuation counts to integers to ensure floats are never used
    • +
    + +

    2.1.1 - 2022-01-02

    + +

    Added

    + +
      +
    • Added missing return type to Environment::dispatch() to fix deprecation warning (#778)
    • +
    + +

    2.1.0 - 2021-12-05

    + +

    Added

    + +
      +
    • Added support for ext-yaml in FrontMatterExtension (#715)
    • +
    • Added support for symfony/yaml v6.0 in FrontMatterExtension (#739)
    • +
    • Added new heading_permalink/aria_hidden config option (#741)
    • +
    + +

    Fixed

    + +
      +
    • Fixed PHP 8.1 deprecation warning (#759, #762)
    • +
    + +

    2.0.2 - 2021-08-14

    + +

    Changed

    + +
      +
    • Bumped minimum version of league/config to support PHP 8.1
    • +
    + +

    Fixed

    + +
      +
    • Fixed ability to register block parsers that identify lines starting with letters (#706)
    • +
    + +

    2.0.1 - 2021-07-31

    + +

    Fixed

    + +
      +
    • Fixed nested autolinks (#689)
    • +
    • Fixed description lists being parsed incorrectly (#692)
    • +
    • Fixed Table of Contents not respecting Heading Permalink prefixes (#690)
    • +
    + +

    2.0.0 - 2021-07-24

    + +

    No changes were introduced since the previous 2.0.0-rc2 release.

    + +

    Please refer to the full Changelog for a list of all changes between 1.x and 2.0. An upgrading guide is also available.

    + +

    2.0.0-rc2 - 2021-07-17

    + +

    Fixed

    + +
      +
    • Fixed Mentions inside of links creating nested links against the spec’s rules (#688)
    • +
    + +

    1.6.6 - 2021-07-17

    + +

    Fixed

    + +
      +
    • Fixed Mentions inside of links creating nested links against the spec’s rules (#688)
    • +
    + +

    2.0.0-rc1 - 2021-07-10

    + +

    No changes were introduced since the previous release.

    + +

    2.0.0-beta3 - 2021-07-03

    + +

    Changed

    + +
      +
    • Any leading UTF-8 BOM will be stripped from the input
    • +
    • The getEnvironment() method of CommonMarkConverter and GithubFlavoredMarkdownConverter will always return the concrete, configurable Environment for upgrading convenience
    • +
    • Optimized AST iteration
    • +
    • Lots of small micro-optimizations
    • +
    + +

    2.0.0-beta2 - 2021-06-27

    + +

    See https://commonmark.thephpleague.com/2.0/upgrading/ for detailed information on upgrading to version 2.0.

    + +

    Added

    + +
      +
    • Added new Node::iterator() method and NodeIterator class for faster AST iteration (#683, #684)
    • +
    + +

    Changed

    + +
      +
    • Made compatible with CommonMark spec 0.30.0
    • +
    • Optimized link label parsing
    • +
    • Optimized AST iteration for a 50% performance boost in some event listeners (#683, #684)
    • +
    + +

    Fixed

    + +
      +
    • Fixed processing instructions with EOLs
    • +
    • Fixed case-insensitive matching for HTML tag types
    • +
    • Fixed type 7 HTML blocks incorrectly interrupting lazy paragraphs
    • +
    • Fixed newlines in reference labels not collapsing into spaces
    • +
    • Fixed link label normalization with escaped newlines
    • +
    • Fixed unnecessary AST iteration when no default attributes are configured
    • +
    + +

    1.6.5 - 2021-06-26

    + +

    Changed

    + +
      +
    • Simplified checks for thematic breaks
    • +
    + +

    Fixed

    + +
      +
    • Fixed ExternalLinkProcessor not handling autolinks by adjusting its priority to -50 (#681)
    • +
    + +

    2.0.0-beta1 - 2021-06-20

    + +

    See https://commonmark.thephpleague.com/2.0/upgrading/ for detailed information on upgrading to version 2.0.

    + +

    Added

    + +
      +
    • Added three new extensions: + +
    • +
    • Added new XmlRenderer to simplify AST debugging (see documentation) (#431)
    • +
    • Added the ability to configure disallowed raw HTML tags (#507)
    • +
    • Added the ability for Mentions to use multiple characters for their symbol (#514, #550)
    • +
    • Added the ability to delegate event dispatching to PSR-14 compliant event dispatcher libraries
    • +
    • Added new configuration options: +
        +
      • Added heading_permalink/min_heading_level and heading_permalink/max_heading_level options to control which headings get permalinks (#519)
      • +
      • Added heading_permalink/fragment_prefix to allow customizing the URL fragment prefix (#602)
      • +
      • Added footnote/backref_symbol option for customizing backreference link appearance (#522)
      • +
      • Added slug_normalizer/max_length option to control the maximum length of generated URL slugs
      • +
      • Added slug_normalizer/unique option to control whether unique slugs should be generated per-document or per-environment
      • +
      +
    • +
    • Added purity markers throughout the codebase (verified with Psalm)
    • +
    • Added Query class to simplify Node traversal when looking to take action on certain Nodes
    • +
    • Added new HtmlFilter and StringContainerHelper utility classes
    • +
    • Added new AbstractBlockContinueParser class to simplify the creation of custom block parsers
    • +
    • Added several new classes and interfaces: +
        +
      • BlockContinue
      • +
      • BlockContinueParserInterface
      • +
      • BlockContinueParserWithInlinesInterface
      • +
      • BlockStart
      • +
      • BlockStartParserInterface
      • +
      • ChildNodeRendererInterface
      • +
      • ConfigurableExtensionInterface
      • +
      • CursorState
      • +
      • DashParser (extracted from PunctuationParser)
      • +
      • DelimiterParser
      • +
      • DocumentBlockParser
      • +
      • DocumentPreRenderEvent
      • +
      • DocumentRenderedEvent
      • +
      • EllipsesParser (extracted from PunctuationParser)
      • +
      • ExpressionInterface
      • +
      • FallbackNodeXmlRenderer
      • +
      • InlineParserEngineInterface
      • +
      • InlineParserMatch
      • +
      • MarkdownParserState
      • +
      • MarkdownParserStateInterface
      • +
      • MarkdownRendererInterface
      • +
      • Query
      • +
      • RawMarkupContainerInterface
      • +
      • ReferenceableInterface
      • +
      • RenderedContent
      • +
      • RenderedContentInterface
      • +
      • ReplaceUnpairedQuotesListener
      • +
      • SpecReader
      • +
      • TableOfContentsRenderer
      • +
      • UniqueSlugNormalizer
      • +
      • UniqueSlugNormalizerInterface
      • +
      • XmlRenderer
      • +
      • XmlNodeRendererInterface
      • +
      +
    • +
    • Added several new methods: +
        +
      • Cursor::getCurrentCharacter()
      • +
      • Environment::createDefaultConfiguration()
      • +
      • Environment::setEventDispatcher()
      • +
      • EnvironmentInterface::getExtensions()
      • +
      • EnvironmentInterface::getInlineParsers()
      • +
      • EnvironmentInterface::getSlugNormalizer()
      • +
      • FencedCode::setInfo()
      • +
      • Heading::setLevel()
      • +
      • HtmlRenderer::renderDocument()
      • +
      • InlineParserContext::getFullMatch()
      • +
      • InlineParserContext::getFullMatchLength()
      • +
      • InlineParserContext::getMatches()
      • +
      • InlineParserContext::getSubMatches()
      • +
      • LinkParserHelper::parsePartialLinkLabel()
      • +
      • LinkParserHelper::parsePartialLinkTitle()
      • +
      • Node::assertInstanceOf()
      • +
      • RegexHelper::isLetter()
      • +
      • StringContainerInterface::setLiteral()
      • +
      • TableCell::getType()
      • +
      • TableCell::setType()
      • +
      • TableCell::getAlign()
      • +
      • TableCell::setAlign()
      • +
      +
    • +
    + +

    Changed

    + +
      +
    • Changed the converter return type +
        +
      • CommonMarkConverter::convertToHtml() now returns an instance of RenderedContentInterface. This can be cast to a string for backward compatibility with 1.x.
      • +
      +
    • +
    • Table of Contents items are no longer wrapped with <p> tags (#613)
    • +
    • Heading Permalinks now link to element IDs instead of using name attributes (#602)
    • +
    • Heading Permalink IDs and URL fragments now have a content prefix by default (#602)
    • +
    • Changes to configuration options: +
        +
      • enable_em has been renamed to commonmark/enable_em
      • +
      • enable_strong has been renamed to commonmark/enable_strong
      • +
      • use_asterisk has been renamed to commonmark/use_asterisk
      • +
      • use_underscore has been renamed to commonmark/use_underscore
      • +
      • unordered_list_markers has been renamed to commonmark/unordered_list_markers
      • +
      • mentions/*/symbol has been renamed to mentions/*/prefix
      • +
      • mentions/*/regex has been renamed to mentions/*/pattern and requires partial regular expressions (without delimiters or flags)
      • +
      • max_nesting_level now defaults to PHP_INT_MAX and no longer supports floats
      • +
      • heading_permalink/slug_normalizer has been renamed to slug_normalizer/instance
      • +
      +
    • +
    • Event dispatching is now fully PSR-14 compliant
    • +
    • Moved and renamed several classes - see the full list here
    • +
    • The HeadingPermalinkExtension and FootnoteExtension were modified to ensure they never produce a slug which conflicts with slugs created by the other extension
    • +
    • SlugNormalizer::normalizer() now supports optional prefixes and max length options passed in via the $context argument
    • +
    • The AbstractBlock::$data and AbstractInline::$data arrays were replaced with a Data array-like object on the base Node class
    • +
    • Implemented a new approach to block parsing. This was a massive change, so here are the highlights: +
        +
      • Functionality previously found in block parsers and node elements has moved to block parser factories and block parsers, respectively (more details)
      • +
      • ConfigurableEnvironmentInterface::addBlockParser() is now EnvironmentBuilderInterface::addBlockParserFactory()
      • +
      • ReferenceParser was re-implemented and works completely different than before
      • +
      • The paragraph parser no longer needs to be added manually to the environment
      • +
      +
    • +
    • Implemented a new approach to inline parsing where parsers can now specify longer strings or regular expressions they want to parse (instead of just single characters): +
        +
      • InlineParserInterface::getCharacters() is now getMatchDefinition() and returns an instance of InlineParserMatch
      • +
      • InlineParserContext::__construct() now requires the contents to be provided as a Cursor instead of a string
      • +
      +
    • +
    • Implemented delimiter parsing as a special type of inline parser (via the new DelimiterParser class)
    • +
    • Changed block and inline rendering to use common methods and interfaces +
        +
      • BlockRendererInterface and InlineRendererInterface were replaced by NodeRendererInterface with slightly different parameters. All core renderers now implement this interface.
      • +
      • ConfigurableEnvironmentInterface::addBlockRenderer() and addInlineRenderer() were combined into EnvironmentBuilderInterface::addRenderer()
      • +
      • EnvironmentInterface::getBlockRenderersForClass() and getInlineRenderersForClass() are now just getRenderersForClass()
      • +
      +
    • +
    • Completely refactored the Configuration implementation +
        +
      • All configuration-specific classes have been moved into a new league/config package with a new namespace
      • +
      • Configuration objects must now be configured with a schema and all options must match that schema - arbitrary keys are no longer permitted
      • +
      • Configuration::__construct() no longer accepts the default configuration values - use Configuration::merge() instead
      • +
      • ConfigurationInterface now only contains a get(string $key); this method no longer allows arbitrary default values to be returned if the option is missing
      • +
      • ConfigurableEnvironmentInterface was renamed to EnvironmentBuilderInterface
      • +
      • ExtensionInterface::register() now requires an EnvironmentBuilderInterface param instead of ConfigurableEnvironmentInterface
      • +
      +
    • +
    • Added missing return types to virtually every class and interface method
    • +
    • Re-implemented the GFM Autolink extension using the new inline parser approach instead of document processors +
        +
      • EmailAutolinkProcessor is now EmailAutolinkParser
      • +
      • UrlAutolinkProcessor is now UrlAutolinkParser
      • +
      +
    • +
    • HtmlElement can now properly handle array (i.e. class) and boolean (i.e. checked) attribute values
    • +
    • HtmlElement automatically flattens any attributes with array values into space-separated strings, removing duplicate entries
    • +
    • Combined separate classes/interfaces into one: +
        +
      • DisallowedRawHtmlRenderer replaces DisallowedRawHtmlBlockRenderer and DisallowedRawHtmlInlineRenderer
      • +
      • NodeRendererInterface replaces BlockRendererInterface and InlineRendererInterface
      • +
      +
    • +
    • Renamed the following methods: +
        +
      • Environment and ConfigurableEnvironmentInterface: +
          +
        • addBlockParser() is now addBlockStartParser()
        • +
        +
      • +
      • ReferenceMap and ReferenceMapInterface: +
          +
        • addReference() is now add()
        • +
        • getReference() is now get()
        • +
        • listReferences() is now getIterator()
        • +
        +
      • +
      • Various node (block/inline) classes: +
          +
        • getContent() is now getLiteral()
        • +
        • setContent() is now setLiteral()
        • +
        +
      • +
      +
    • +
    • Moved and renamed the following constants: +
        +
      • EnvironmentInterface::HTML_INPUT_ALLOW is now HtmlFilter::ALLOW
      • +
      • EnvironmentInterface::HTML_INPUT_ESCAPE is now HtmlFilter::ESCAPE
      • +
      • EnvironmentInterface::HTML_INPUT_STRIP is now HtmlFilter::STRIP
      • +
      • TableCell::TYPE_HEAD is now TableCell::TYPE_HEADER
      • +
      • TableCell::TYPE_BODY is now TableCell::TYPE_DATA
      • +
      +
    • +
    • Changed the visibility of the following properties: +
        +
      • AttributesInline::$attributes is now private
      • +
      • AttributesInline::$block is now private
      • +
      • TableCell::$align is now private
      • +
      • TableCell::$type is now private
      • +
      • TableSection::$type is now private
      • +
      +
    • +
    • Several methods which previously returned $this now return void +
        +
      • Delimiter::setPrevious()
      • +
      • Node::replaceChildren()
      • +
      • Context::setTip()
      • +
      • Context::setContainer()
      • +
      • Context::setBlocksParsed()
      • +
      • AbstractStringContainer::setContent()
      • +
      • AbstractWebResource::setUrl()
      • +
      +
    • +
    • Several classes are now marked final: +
        +
      • ArrayCollection
      • +
      • Emphasis
      • +
      • FencedCode
      • +
      • Heading
      • +
      • HtmlBlock
      • +
      • HtmlElement
      • +
      • HtmlInline
      • +
      • IndentedCode
      • +
      • Newline
      • +
      • Strikethrough
      • +
      • Strong
      • +
      • Text
      • +
      +
    • +
    • Heading nodes no longer directly contain a copy of their inner text
    • +
    • StringContainerInterface can now be used for inlines, not just blocks
    • +
    • ArrayCollection only supports integer keys
    • +
    • HtmlElement now implements Stringable
    • +
    • Cursor::saveState() and Cursor::restoreState() now use CursorState objects instead of arrays
    • +
    • NodeWalker::next() now enters, traverses any children, and leaves all elements which may have children (basically all blocks plus any inlines with children). Previously, it only did this for elements explicitly marked as “containers”.
    • +
    • InvalidOptionException was removed
    • +
    • Anything with a getReference(): ReferenceInterface method now implements ReferencableInterface
    • +
    • The SmartPunct extension now replaces all unpaired Quote elements with Text elements towards the end of parsing, making the QuoteRenderer unnecessary
    • +
    • Several changes made to the Footnote extension: +
        +
      • Footnote identifiers can no longer contain spaces
      • +
      • Anonymous footnotes can now span subsequent lines
      • +
      • Footnotes can now contain multiple lines of content, including sub-blocks, by indenting them
      • +
      • Footnote event listeners now have numbered priorities (but still execute in the same order)
      • +
      • Footnotes must now be separated from previous content by a blank line
      • +
      +
    • +
    • The line numbers (keys) returned via MarkdownInput::getLines() now start at 1 instead of 0
    • +
    • DelimiterProcessorCollectionInterface now extends Countable
    • +
    • RegexHelper::PARTIAL_ constants must always be used in case-insensitive contexts
    • +
    • HeadingPermalinkProcessor no longer accepts text normalizers via the constructor - these must be provided via configuration instead
    • +
    • Blocks which can’t contain inlines will no longer be asked to render inlines
    • +
    • AnonymousFootnoteRefParser and HeadingPermalinkProcessor now implement EnvironmentAwareInterface instead of ConfigurationAwareInterface
    • +
    • The second argument to TextNormalizerInterface::normalize() must now be an array
    • +
    • The title attribute for Link and Image nodes is now stored using a dedicated property instead of stashing it in $data
    • +
    • ListData::$delimiter now returns either ListBlock::DELIM_PERIOD or ListBlock::DELIM_PAREN instead of the literal delimiter
    • +
    + +

    Fixed

    + +
      +
    • Fixed parsing of footnotes without content
    • +
    • Fixed rendering of orphaned footnotes and footnote refs
    • +
    • Fixed some URL autolinks breaking too early (#492)
    • +
    • Fixed AbstractStringContainer not actually being abstract
    • +
    + +

    Removed

    + +
      +
    • Removed support for PHP 7.1, 7.2, and 7.3 (#625, #671)
    • +
    • Removed all previously-deprecated functionality: +
        +
      • Removed the ability to pass custom Environment instances into the CommonMarkConverter and GithubFlavoredMarkdownConverter constructors
      • +
      • Removed the Converter class and ConverterInterface
      • +
      • Removed the bin/commonmark script
      • +
      • Removed the Html5Entities utility class
      • +
      • Removed the InlineMentionParser (use MentionParser instead)
      • +
      • Removed DefaultSlugGenerator and SlugGeneratorInterface from the Extension/HeadingPermalink/Slug sub-namespace (use the new ones under ./SlugGenerator instead)
      • +
      • Removed the following ArrayCollection methods: +
          +
        • add()
        • +
        • set()
        • +
        • get()
        • +
        • remove()
        • +
        • isEmpty()
        • +
        • contains()
        • +
        • indexOf()
        • +
        • containsKey()
        • +
        • replaceWith()
        • +
        • removeGaps()
        • +
        +
      • +
      • Removed the ConfigurableEnvironmentInterface::setConfig() method
      • +
      • Removed the ListBlock::TYPE_UNORDERED constant
      • +
      • Removed the CommonMarkConverter::VERSION constant
      • +
      • Removed the HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant
      • +
      • Removed the heading_permalink/inner_contents configuration option
      • +
      +
    • +
    • Removed now-unused classes: +
        +
      • AbstractStringContainerBlock
      • +
      • BlockRendererInterface
      • +
      • Context
      • +
      • ContextInterface
      • +
      • Converter
      • +
      • ConverterInterface
      • +
      • InlineRendererInterface
      • +
      • PunctuationParser (was split into two classes: DashParser and EllipsesParser)
      • +
      • QuoteRenderer
      • +
      • UnmatchedBlockCloser
      • +
      +
    • +
    • Removed the following methods, properties, and constants: +
        +
      • AbstractBlock::$open
      • +
      • AbstractBlock::$lastLineBlank
      • +
      • AbstractBlock::isContainer()
      • +
      • AbstractBlock::canContain()
      • +
      • AbstractBlock::isCode()
      • +
      • AbstractBlock::matchesNextLine()
      • +
      • AbstractBlock::endsWithBlankLine()
      • +
      • AbstractBlock::setLastLineBlank()
      • +
      • AbstractBlock::shouldLastLineBeBlank()
      • +
      • AbstractBlock::isOpen()
      • +
      • AbstractBlock::finalize()
      • +
      • AbstractBlock::getData()
      • +
      • AbstractInline::getData()
      • +
      • ConfigurableEnvironmentInterface::addBlockParser()
      • +
      • ConfigurableEnvironmentInterface::mergeConfig()
      • +
      • Delimiter::setCanClose()
      • +
      • EnvironmentInterface::getConfig()
      • +
      • EnvironmentInterface::getInlineParsersForCharacter()
      • +
      • EnvironmentInterface::getInlineParserCharacterRegex()
      • +
      • HtmlRenderer::renderBlock()
      • +
      • HtmlRenderer::renderBlocks()
      • +
      • HtmlRenderer::renderInline()
      • +
      • HtmlRenderer::renderInlines()
      • +
      • Node::isContainer()
      • +
      • RegexHelper::matchAll() (use the new matchFirst() method instead)
      • +
      • RegexHelper::REGEX_WHITESPACE
      • +
      +
    • +
    • Removed the second $contents argument from the Heading constructor
    • +
    + +

    Deprecated

    + +

    The following things have been deprecated and will not be supported in v3.0:

    + +
      +
    • Environment::mergeConfig() (set configuration before instantiation instead)
    • +
    • Environment::createCommonMarkEnvironment() and Environment::createGFMEnvironment() +
        +
      • Alternative 1: Use CommonMarkConverter or GithubFlavoredMarkdownConverter if you don’t need to customize the environment
      • +
      • Alternative 2: Instantiate a new Environment and add the necessary extensions yourself
      • +
      +
    • +
    + +

    1.6.4 - 2021-06-19

    + +

    Changed

    + +
      +
    • Optimized attribute parsing to avoid inspecting every space character (30% performance boost)
    • +
    + +

    1.6.3 - 2021-06-19

    + +

    Fixed

    + +
      +
    • Fixed incorrect parsing of tilde-fenced code blocks with leading spaces (#676)
    • +
    + +

    1.6.2 - 2021-05-12

    + +

    Fixed

    + +
      +
    • Fixed incorrect error level for deprecation notices
    • +
    + +

    1.6.1 - 2021-05-08

    + +

    Fixed

    + +
      +
    • Fixed HeadingPermalinkProcessor skipping text contents from certain nodes (#615)
    • +
    + +

    1.6.0 - 2021-05-01

    + +

    Please see https://commonmark.thephpleague.com/1.6/upgrading/ for important information about this release and the upcoming 2.0.0 version.

    + +

    Added

    + +
      +
    • Added forward-compatibility for configuration options which will be changing in 2.0: +
        +
      • commonmark/enable_em (currently enable_em in 1.x)
      • +
      • commonmark/enable_strong (currently enable_strong in 1.x)
      • +
      • commonmark/use_asterisk (currently use_asterisk in 1.x)
      • +
      • commonmark/use_underscore (currently use_underscore in 1.x)
      • +
      • commonmark/unordered_list_markers (currently unordered_list_markers in 1.x)
      • +
      • mentions/*/prefix (currently mentions/*/symbol in 1.x)
      • +
      • mentions/*/pattern (currently mentions/*/regex in 1.x)
      • +
      • max_nesting_level (currently supports int and float values in 1.x; will only support int in 2.0)
      • +
      +
    • +
    • Added new MarkdownConverter class for creating converters with custom environments; this replaces the previously-deprecated Converter class
    • +
    • Added new RegexHelper::matchFirst() method
    • +
    • Added new Configuration::exists() method
    • +
    + +

    Changed

    + +
      +
    • The max_nesting_level option now defaults to PHP_INT_MAX instead of INF
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated the configuration options shown above
    • +
    • Deprecated the ability to pass a custom Environment into the constructors of CommonMarkConverter and GithubFlavoredMarkdownConverter; use MarkdownConverter instead
    • +
    • Deprecated ConfigurableEnvironmentInterface::setConfig(); use mergeConfig() instead
    • +
    • Deprecated calling ConfigurableEnvironmentInterface::mergeConfig() without any parameters
    • +
    • Deprecated calling Configuration::get() and EnvironmentInterface::getConfig() without any parameters
    • +
    • Deprecated calling Configuration::set() without the second $value parameter
    • +
    • Deprecated RegexHelper::matchAll(); use RegexHelper::matchFirst() instead
    • +
    • Deprecated extending the ArrayCollection class; will be marked final in 2.0
    • +
    + +

    Fixed

    + +
      +
    • Fixed missing check for empty arrays being passed into the unordered_list_markers configuration option
    • +
    + +

    1.5.8 - 2021-03-28

    + +

    Fixed

    + +
      +
    • Fixed Table of Contents not rendering heading inlines properly (#587, #588)
    • +
    • Fixed parsing of tables within list items (#590)
    • +
    + +

    1.5.7 - 2020-10-31

    + +

    Fixed

    + +
      +
    • Fixed mentions not being parsed when appearing after non-word characters (#582)
    • +
    + +

    1.5.6 - 2020-10-17

    + +

    Changed

    + +
      +
    • Blocks added outside of the parsing context now have their start/end line numbers defaulted to 0 to avoid type errors (#579)
    • +
    + +

    Fixed

    + +
      +
    • Fixed replacement blocks not inheriting the start line number of the container they’re replacing (#579)
    • +
    • Fixed Table of Contents blocks not having correct start/end line numbers (#579)
    • +
    + +

    1.5.5 - 2020-09-13

    + +

    Changed

    + +
      +
    • Bumped CommonMark spec compliance to 0.28.2
    • +
    + +

    Fixed

    + +
      +
    • Fixed textarea elements not being treated as a type 1 HTML block (like script, style, or pre)
    • +
    • Fixed autolink processor not handling other unmatched trailing parentheses
    • +
    + +

    1.5.4 - 2020-08-18

    + +

    Fixed

    + +
      +
    • Fixed footnote ID configuration not taking effect (#524, #530)
    • +
    • Fixed heading permalink slugs not being unique (#531, #534)
    • +
    + +

    1.5.3 - 2020-07-19

    + +

    Fixed

    + +
      +
    • Fixed regression of multi-byte inline parser characters not being matched
    • +
    + +

    1.5.2 - 2020-07-19

    + +

    Changed

    + +
      +
    • Significantly improved performance of the inline parser regex
    • +
    + +

    Fixed

    + +
      +
    • Fixed parent class lookups for non-existent classes on PHP 8 (#517)
    • +
    + +

    1.5.1 - 2020-06-27

    + +

    Fixed

    + +
      +
    • Fixed UTF-8 encoding not being checked in the UrlEncoder utility (#509) or the Cursor
    • +
    + +

    1.5.0 - 2020-06-21

    + +

    Added

    + +
      +
    • Added new AttributesExtension based on https://github.com/webuni/commonmark-attributes-extension (#474)
    • +
    • Added new FootnoteExtension based on https://github.com/rezozero/commonmark-ext-footnotes (#474)
    • +
    • Added new MentionExtension to replace InlineMentionParser with more flexibility and customization
    • +
    • Added the ability to render TableOfContents nodes anywhere in a document (given by a placeholder)
    • +
    • Added the ability to properly clone Node objects
    • +
    • Added options to customize the value of rel attributes set via the ExternalLink extension (#476)
    • +
    • Added a new heading_permalink/slug_normalizer configuration option to allow custom slug generation (#460)
    • +
    • Added a new heading_permalink/symbol configuration option to replace the now deprecated heading_permalink/inner_contents configuration option (#505)
    • +
    • Added SlugNormalizer and TextNormalizer classes to make normalization reusable by extensions (#485)
    • +
    • Added new classes: +
        +
      • TableOfContentsGenerator
      • +
      • TableOfContentsGeneratorInterface
      • +
      • TableOfContentsPlaceholder
      • +
      • TableOfContentsPlaceholderParser
      • +
      • TableOfContentsPlaceholderRenderer
      • +
      +
    • +
    + +

    Changed

    + +
      +
    • “Moved” the TableOfContents class into a new Node sub-namespace (with backward-compatibility)
    • +
    • Reference labels are now generated and stored in lower-case instead of upper-case
    • +
    • Reference labels are no longer normalized inside the Reference, only the ReferenceMap
    • +
    + +

    Fixed

    + +
      +
    • Fixed reference label case folding polyfill not being consistent between different PHP versions
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated the CommonMarkConverter::VERSION constant (#496)
    • +
    • Deprecated League\CommonMark\Extension\Autolink\InlineMentionParser (use League\CommonMark\Extension\Mention\MentionParser instead)
    • +
    • Deprecated everything under League\CommonMark\Extension\HeadingPermalink\Slug (use the classes under League\CommonMark\Normalizer instead)
    • +
    • Deprecated League\CommonMark\Extension\TableOfContents\TableOfContents (use the one in the new Node sub-namespace instead)
    • +
    • Deprecated the STYLE_ and NORMALIZE_ constants in TableOfContentsBuilder (use the ones in TableOfContentsGenerator instead)
    • +
    • Deprecated the \League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS constant (#505)
    • +
    • Deprecated the heading_permalink/inner_contents configuration option in the HeadingPermalink extension (use the new heading_permalink/symbol configuration option instead) (#505)
    • +
    + +

    1.4.3 - 2020-05-04

    + +

    Fixed

    + +
      +
    • Fixed certain Unicode letters, numbers, and marks not being preserved when generating URL slugs (#467)
    • +
    + +

    1.4.2 - 2020-04-24

    + +

    Fixed

    + +
      +
    • Fixed inline code blocks not be included within heading permalinks (#457)
    • +
    + +

    1.4.1 - 2020-04-20

    + +

    Fixed

    + +
      +
    • Fixed BC break caused by ConverterInterface alias not being used by some DI containers (#454)
    • +
    + +

    1.4.0 - 2020-04-18

    + +

    Added

    + +
      +
    • Added new Heading Permalink extension (#420)
    • +
    • Added new Table of Contents extension (#441)
    • +
    • Added new MarkdownConverterInterface as a long-term replacement for ConverterInterface (#439)
    • +
    • Added new DocumentPreParsedEvent event (#427, #359, #399)
    • +
    • Added new ListBlock::TYPE_BULLET constant as a replacement for ListBlock::TYPE_UNORDERED
    • +
    • Added new MarkdownInput class and MarkdownInputInterface to handle pre-parsing and allow listeners to replace Markdown contents
    • +
    + +

    Changed

    + +
      +
    • Block & inline renderers will now render child classes automatically (#222, #209)
    • +
    • The ListBlock constants now use fully-lowercased values instead of titlecased values
    • +
    • Significantly improved typing
    • +
    + +

    Fixed

    + +
      +
    • Fixed loose comparison when checking for table alignment
    • +
    • Fixed StaggeredDelimiterProcessor returning from a void function
    • +
    + +

    Deprecated

    + +
      +
    • The Converter class has been deprecated; use CommonMarkConverter instead (#438, #439)
    • +
    • The ConverterInterface has been deprecated; use MarkdownConverterInterface instead (#438, #439)
    • +
    • The bin/commonmark script has been deprecated
    • +
    • The following methods of ArrayCollection have been deprecated: +
        +
      • add()
      • +
      • set()
      • +
      • get()
      • +
      • remove()
      • +
      • isEmpty()
      • +
      • contains()
      • +
      • indexOf()
      • +
      • containsKey()
      • +
      • replaceWith()
      • +
      • removeGaps()
      • +
      +
    • +
    • The ListBlock::TYPE_UNORDERED constant has been deprecated, use ListBlock::TYPE_BULLET instead
    • +
    + +

    1.3.4 - 2020-04-13

    + +

    Fixed

    + +
      +
    • Fixed configuration/environment not being injected into event listeners when adding them via [$instance, 'method'] callable syntax (#440)
    • +
    + +

    1.3.3 - 2020-04-05

    + +

    Fixed

    + +
      +
    • Fixed event listeners not having the environment or configuration injected if they implemented the EnvironmentAwareInterface or ConfigurationAwareInterface (#423)
    • +
    + +

    1.3.2 - 2020-03-25

    + +

    Fixed

    + +
      +
    • Optimized URL normalization in cases where URLs don’t contain special characters (#417, #418)
    • +
    + +

    1.3.1 - 2020-02-28

    + +

    Fixed

    + +
      +
    • Fixed return types of Environment::createCommonMarkEnvironment() and Environment::createGFMEnvironment()
    • +
    + +

    1.3.0 - 2020-02-09

    + +

    ℹ️ Do you use league/commonmark-ext* packages? Those features are now included directly in this library! See #409 for details on making the switch.

    + +

    Added

    + +
      +
    • Added (optional) full GFM support! 🎉🎉🎉 (#409)
    • +
    • Added check to ensure Markdown input is valid UTF-8 (#401, #405)
    • +
    • Added new unordered_list_markers configuration option (#408, #411)
    • +
    + +

    Changed

    + +
      +
    • Introduced several micro-optimizations for a 5-10% performance boost
    • +
    + +

    1.2.2 - 2020-01-16

    + +

    This release contains the same changes as 1.1.3:

    + +

    Fixed

    + +
      +
    • Fixed link parsing edge case (#403)
    • +
    + +

    1.1.3 - 2020-01-16

    + +

    Fixed

    + +
      +
    • Fixed link parsing edge case (#403)
    • +
    + +

    1.2.1 - 2020-01-15

    + +

    Changed

    + +
      +
    • Introduced several micro-optimizations, reducing the parse time by 8%
    • +
    + +

    1.2.0 - 2020-01-09

    + +

    Changed

    + +
      +
    • Removed URL decoding step before encoding (more performant and better matches the JS library)
    • +
    • Removed redundant token from HTML tag regex
    • +
    + +

    1.1.2 - 2019-12-10

    + +

    Fixed

    + +
      +
    • Fixed URL normalization not handling non-UTF-8 sequences properly (#395, #396)
    • +
    + +

    1.1.1 - 2019-11-11

    + +

    Fixed

    + +
      +
    • Fixed handling of link destinations with unbalanced unescaped parens
    • +
    • Fixed adding delimiters to stack which can neither open nor close a run
    • +
    + +

    1.1.0 - 2019-10-31

    + +

    Added

    + +
      +
    • Added a new Html5EntityDecoder class (#387)
    • +
    + +

    Changed

    + +
      +
    • Improved performance by 10% (#389)
    • +
    • Made entity decoding less memory-intensive (#386, #387)
    • +
    + +

    Fixed

    + +
      +
    • Fixed PHP 7.4 compatibility issues
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated the Html5Entities class - use Html5EntityDecoder instead (#387)
    • +
    + +

    1.0.0 - 2019-06-29

    + +

    First stable release! 🎉

    + +

    No code changes have been introduced since 1.0.0-rc1

    + +

    1.0.0-rc1 - 2019-06-20

    + +

    Added

    + +
      +
    • Extracted a ReferenceMapInterface from the ReferenceMap class
    • +
    • Added optional ReferenceMapInterface parameter to the Document constructor
    • +
    + +

    Changed

    + +
      +
    • Replaced all references to ReferenceMap with ReferenceMapInterface
    • +
    • ReferenceMap::addReference() no longer returns $this
    • +
    + +

    Fixed

    + +
      +
    • Fixed bug where elements with content of "0" wouldn’t be rendered (#376)
    • +
    + +

    0.19.3 - 2019-06-18

    + +

    Fixed

    + +
      +
    • Fixed bug where elements with content of "0" wouldn’t be rendered (#376)
    • +
    + +

    1.0.0-beta4 - 2019-06-05

    + +

    Added

    + +
      +
    • Added event dispatcher functionality (#359, #372)
    • +
    + +

    Removed

    + +
      +
    • Removed DocumentProcessorInterface functionality in favor of event dispatching (#373)
    • +
    + +

    1.0.0-beta3 - 2019-05-28

    + +

    Changed

    + +
      +
    • Made the Delimiter class final and extracted a new DelimiterInterface +
        +
      • Modified most external usages to use this new interface
      • +
      +
    • +
    • Renamed three Delimiter methods: +
        +
      • getOrigDelims() renamed to getOriginalLength()
      • +
      • getNumDelims() renamed to getLength()
      • +
      • setNumDelims() renamed to setLength()
      • +
      +
    • +
    • Made additional classes final: +
        +
      • DelimiterStack
      • +
      • ReferenceMap
      • +
      • ReferenceParser
      • +
      +
    • +
    • Moved ReferenceParser into the Reference sub-namespace
    • +
    + +

    Removed

    + +
      +
    • Removed unused Delimiter methods: +
        +
      • setCanOpen()
      • +
      • setCanClose()
      • +
      • setChar()
      • +
      • setIndex()
      • +
      • setInlineNode()
      • +
      +
    • +
    • Removed fluent interface from Delimiter (setter methods now have no return values)
    • +
    + +

    1.0.0-beta2 - 2019-05-27

    + +

    This beta release fixes a couple of items that were not addressed in the previous beta.

    + +

    Changed

    + +
      +
    • DelimiterProcessorInterface::process() will accept any type of AbstractStringContainer now, not just Text nodes
    • +
    • The Delimiter constructor, getInlineNode(), and setInlineNode() no longer accept generic Node elements - only AbstractStringContainers
    • +
    + +

    Removed

    + +
      +
    • Removed all deprecated functionality: +
        +
      • The safe option (use html_input and allow_unsafe_links options instead)
      • +
      • All deprecated RegexHelper constants
      • +
      • DocParser::getEnvironment() (you should obtain it some other way)
      • +
      • AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
      • +
      +
    • +
    + +

    1.0.0-beta1 - 2019-05-26

    + +

    See the upgrading guide for additional information.

    + +

    Added

    + +
      +
    • Added proper support for delimiters, including custom delimiters +
        +
      • addDelimiterProcessor() added to ConfigurableEnvironmentInterface and Environment
      • +
      +
    • +
    • Basic delimiters no longer need custom parsers - they’ll be parsed automatically
    • +
    • Added new methods: +
        +
      • AdjacentTextMerger::mergeTextNodesBetweenExclusive()
      • +
      • CommonMarkConveter::getEnvironment()
      • +
      • Configuration::set()
      • +
      +
    • +
    • Extracted some new interfaces from base classes: +
        +
      • DocParserInterface created from DocParser
      • +
      • ConfigurationInterface created from Configuration
      • +
      • ReferenceInterface created from Reference
      • +
      +
    • +
    + +

    Changed

    + +
      +
    • Renamed several methods of the Configuration class: +
        +
      • getConfig() renamed to get()
      • +
      • mergeConfig() renamed to merge()
      • +
      • setConfig() renamed to replace()
      • +
      +
    • +
    • Changed ConfigurationAwareInterface::setConfiguration() to accept the new ConfigurationInterface instead of the concrete class
    • +
    • Renamed the AdjoiningTextCollapser class to AdjacentTextMerger +
        +
      • Replaced its collapseTextNodes() method with the new mergeChildNodes() method
      • +
      +
    • +
    • Made several classes final: +
        +
      • Configuration
      • +
      • DocParser
      • +
      • HtmlRenderer
      • +
      • InlineParserEngine
      • +
      • NodeWalker
      • +
      • Reference
      • +
      • All of the block/inline parsers and renderers
      • +
      +
    • +
    • Reduced visibility of several internal methods to private: +
        +
      • DelimiterStack::findEarliest()
      • +
      • All protected methods in InlineParserEngine
      • +
      +
    • +
    • Marked some classes and methods as @internal
    • +
    • ElementRendererInterface now requires a public renderInline() method; added this to HtmlRenderer
    • +
    • Changed InlineParserEngine::parse() to require an AbstractStringContainerBlock instead of the generic Node class
    • +
    • Un-deprecated the CommonmarkConverter::VERSION constant
    • +
    • The Converter constructor now requires an instance of DocParserInterface instead of the concrete DocParser
    • +
    • Changed Emphasis, Strong, and AbstractWebResource to directly extend AbstractInline instead of the (now-deprecated) intermediary AbstractInlineContainer class
    • +
    + +

    Fixed

    + +
      +
    • Fixed null errors when inserting sibling Nodes without parents
    • +
    • Fixed NodeWalkerEvent not requiring a Node via its constructor
    • +
    • Fixed Reference::normalizeReference() improperly converting to uppercase instead of performing proper Unicode case-folding
    • +
    • Fixed strong emphasis delimiters not being preserved when enable_strong is set to false (it now works identically to enable_em)
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated DocParser::getEnvironment() (you should obtain it some other way)
    • +
    • Deprecated AbstractInlineContainer (use AbstractInline instead and make isContainer() return true)
    • +
    + +

    Removed

    + +
      +
    • Removed inline processor functionality now that we have proper delimiter support: +
        +
      • Removed addInlineProcessor() from ConfigurableEnvironmentInterface and Environment
      • +
      • Removed getInlineProcessors() from EnvironmentInterface and Environment
      • +
      • Removed EmphasisProcessor
      • +
      • Removed InlineProcessorInterface
      • +
      +
    • +
    • Removed EmphasisParser now that we have proper delimiter support
    • +
    • Removed support for non-UTF-8-compatible encodings +
        +
      • Removed getEncoding() from ContextInterface
      • +
      • Removed getEncoding(), setEncoding(), and $encoding from Context
      • +
      • Removed getEncoding() and the second $encoding constructor param from Cursor
      • +
      +
    • +
    • Removed now-unused methods +
        +
      • Removed DelimiterStack::getTop() (no replacement)
      • +
      • Removed DelimiterStack::iterateByCharacters() (use the new processDelimiters() method instead)
      • +
      • Removed the protected DelimiterStack::findMatchingOpener() method
      • +
      +
    • +
    + +

    0.19.2 - 2019-05-19

    + +

    Fixed

    + +
      +
    • Fixed bug where default values for nested configuration paths were inadvertently cast to strings
    • +
    + +

    0.19.1 - 2019-04-11

    + +

    0.19.1 is an immediate follow-up to 0.19.0 which fixes issues with extensions that register other extensions.

    + +

    (While this technically introduces a BC-break, it’s allowed under SemVer’s rules for 0.x releases and is necessary for 0.19.x code to work as expected.)

    + +

    Added

    + +
      +
    • Added the missing addExtension() method to the new ConfigurableEnvironmentInterface
    • +
    + +

    Fixed

    + +
      +
    • Fixed extensions not being able to register other extensions
    • +
    + +

    0.19.0 - 2019-04-11

    + +

    The 50th release of league/commonmark is here! :tada:

    + +

    The Environment and extension framework underwent some major changes in this release. Be sure to read the upgrade notes if you maintain any community extensions or have written custom functionality on top of this library.

    + +

    Added

    + +
      +
    • The priority of parsers, processors, and renderers can now be set when add()ing them; you no longer need to rely on the order in which they are added
    • +
    • Added support for trying multiple parsers per block/inline
    • +
    • Extracted two new base interfaces from Environment: +
        +
      • EnvironmentInterface
      • +
      • ConfigurableEnvironmentInterface
      • +
      +
    • +
    • Extracted a new AbstractStringContainerBlock base class and corresponding StringContainerInterface from AbstractBlock
    • +
    • Added Cursor::getEncoding() method
    • +
    • Added .phpstorm.meta.php file for better IDE code completion
    • +
    • Made some minor optimizations here and there
    • +
    + +

    Changed

    + +
      +
    • Pretty much everything now has parameter and return types (#346)
    • +
    • Attributes passed to HtmlElement will now be escaped by default
    • +
    • Environment is now a final class
    • +
    • Environment::getBlockRendererForClass() was replaced with Environment::getBlockRenderersForClass() (note the added s)
    • +
    • Environment::getInlineRendererForClass() was replaced with Environment::getInlineRenderersForClass() (note the added s)
    • +
    • The Environment::get____() methods now return an iterator instead of an array
    • +
    • Context::addBlock() no longer returns the same block instance you passed into the method, as this served no useful purpose
    • +
    • RegexHelper::isEscapable() no longer accepts null values
    • +
    • Node::replaceChildren() now accepts any type of iterable, not just arrays
    • +
    • Some block elements now extend AbstractStringContainerBlock instead of AbstractBlock +
        +
      • InlineContainerInterface now extends the new StringContainerInterface
      • +
      • The handleRemainingContents() method (formerly on AbstractBlock, now on AbstractStringContainerBlock) is now an `abstract method
      • +
      • The InlineParserContext constructor now requires an AbstractStringContainerBlock instead of an AbstractBlock
      • +
      +
    • +
    + +

    Removed

    + +
      +
    • Removed support for PHP 5.6 and 7.0 (#346)
    • +
    • Removed support for add()ing parsers with just the target block/inline class name - you need to include the full namespace now
    • +
    • Removed the following unused methods from Environment: +
        +
      • getInlineParser($name)
      • +
      • getInlineParsers()
      • +
      • createInlineParserEngine()
      • +
      +
    • +
    • Removed the unused getName() methods: +
        +
      • AbstractBlockParser::getName()
      • +
      • AbstractInlineParser::getName()
      • +
      • BlockParserInterface::getName()
      • +
      • InlinerParserInterface::getName()
      • +
      +
    • +
    • Removed the now-useless classes: +
        +
      • AbstractBlockParser
      • +
      • AbstractInlinerParser
      • +
      • InlineContainer
      • +
      +
    • +
    • Removed the AbstractBlock::acceptsLines() method
    • +
    • Removed the now-useless constructor from AbstractBlock
    • +
    • Removed previously-deprecated functionality: +
        +
      • InlineContainer class
      • +
      • RegexHelper::$instance
      • +
      • RegexHelper::getInstance()
      • +
      • RegexHelper::getPartialRegex()
      • +
      • RegexHelper::getHtmlTagRegex()
      • +
      • RegexHelper::getLinkTitleRegex()
      • +
      • RegexHelper::getLinkDestinationBracesRegex()
      • +
      • RegexHelper::getThematicBreakRegex()
      • +
      +
    • +
    • Removed the second $preserveEntities parameter from Xml:escape()
    • +
    + +

    0.18.5 - 2019-04-09

    + +

    Fixed

    + +
      +
    • Fixed the adjoining Text collapser not handling the full tree (thephpleague/commonmark-ext-autolink#10)
    • +
    + +

    0.18.4 - 2019-03-24

    + +

    Changed

    + +
      +
    • Modified how URL normalization decodes certain characters in order to align with the JS library’s output
    • +
    • Disallowed unescaped ( in parenthesized link title
    • +
    + +

    Fixed

    + +
      +
    • Fixed two exponential backtracking issues
    • +
    + +

    0.18.3 - 2019-03-21

    + +

    This is a security update release.

    + +

    Changed

    + +
      +
    • XML/HTML entities in attributes will no longer be preserved when rendering (#353)
    • +
    + +

    Fixed

    + +
      +
    • Fix XSS vulnerability caused by improper preservation of entities when rendering (#353)
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated the $preserveEntites argument of Xml::escape() for removal in the next release (#353)
    • +
    + +

    0.18.2 - 2019-03-17

    + +

    Fixed

    + +
      +
    • Fixed adjoining Text elements not being collapsed after delimiter processing
    • +
    + +

    Deprecated

    + +
      +
    • Deprecated the CommonmarkConverter::VERSION constant for removal in 1.0.0
    • +
    + +

    0.18.1 - 2018-12-30

    + +

    This release contains an important security update for CVE-2018-20583.

    + +

    Fixed

    + +
      +
    • Fix XSS vulnerability caused by URL normalization not handling/encoding newlines properly (#337, CVE-2018-20583)
    • +
    + +

    0.18.0 - 2018-09-18

    + +

    No breaking changes were introduced, but we did add a new interface: ConverterInterface. Consider depending on this interface in your code instead of the concrete implementation. (See #330)

    + +

    Added

    + +
      +
    • Added ConverterInterface to Converter and CommonMarkConverter (#330)
    • +
    • Added ListItem::getListData() method (#329)
    • +
    + +

    Changed

    + +
      +
    • Links with target="_blank" will also get rel="noopener noreferrer" by default (#331)
    • +
    • Implemented several performance optimizations (#324)
    • +
    + +

    0.17.5 - 2018-03-29

    + +

    Fixed

    + +
      +
    • Fixed incorrect version constant value (again)
    • +
    • Fixed release checklist to prevent the above from happening
    • +
    • Fixed incorrect dates in CHANGELOG
    • +
    + +

    0.17.4 - 2018-03-29

    + +

    Added

    + +
      +
    • Added ListBlock::setTight() method
    • +
    + +

    0.17.3 - 2018-03-26

    + +

    Fixed

    + +
      +
    • Fixed incorrect version constant value (#322)
    • +
    + +

    0.17.2 - 2018-03-26

    + +

    Added

    + +
      +
    • Added new RegexHelper::isEscapable() method
    • +
    + +

    Fixed

    + +
      +
    • Fixed spec compliance bug where escaped spaces should not be allowed in link destinations
    • +
    + +

    0.17.1 - 2018-03-18

    + +

    Added

    + +
      +
    • Added a new constant containing the current version: CommonMarkConverter::VERSION (#314)
    • +
    + +

    0.17.0 - 2017-12-30

    + +

    This release contains several breaking changes and a minimum PHP version bump - see UPGRADE.md for more details.

    + +

    Added

    + +
      +
    • Added new max_nesting_level setting (#243)
    • +
    • Added minor performance optimizations to Cursor
    • +
    + +

    Changed

    + +
      +
    • Minimum PHP version is now 5.6.5.
    • +
    • All full and partial regular expressions in RegexHelper are now defined as constants instead of being built on-the-fly.
    • +
    • Cursor::saveState() now returns an array instead of a CursorState object.
    • +
    • Cursor::restoreState() now accepts an array parameter instead of a CursorState object.
    • +
    • Saving/restoring the Cursor state no longer tracks things that don’t change (like the text content).
    • +
    • RegexHelper is now final.
    • +
    • References to InlineContainer changed to new InlineContainerInterface interface.
    • +
    • MiscExtension::addInlineParser() and MiscExtension::addBlockRenderer() now return $this instead of nothing.
    • +
    + +

    Fixed

    +
      +
    • Fixed Reference::normalizeReference() not properly collapsing whitespace to a single space
    • +
    + +

    Deprecated

    + +
      +
    • RegexHelper::getInstance() and all instance (non-static) methods have been deprecated.
    • +
    • The InlineContainer interface has been deprecated. Use InlineContainerInterface instead.
    • +
    + +

    Removed

    + +
      +
    • Removed support for PHP 5.4 and 5.5.
    • +
    • Removed CursorState class
    • +
    • Removed all previous deprecations: +
        +
      • Cursor::getFirstNonSpacePosition()
      • +
      • Cursor::getFirstNonSpaceCharacter()
      • +
      • Cursor::advanceWhileMatches()
      • +
      • Cursor::advanceToFirstNonSpace()
      • +
      • ElementRendererInterface::escape()
      • +
      • HtmlRenderer::escape()
      • +
      • RegexHelper::REGEX_UNICODE_WHITESPACE
      • +
      • RegexHelper::getLinkDestinationRegex()
      • +
      +
    • +
    + +

    0.16.0 - 2017-10-31

    + +

    This release contains breaking changes, several performance improvements, and two deprecations:

    + +

    Added

    + +
      +
    • Added new Xml utility class; moved HTML/XML escaping logic into there (see deprecations below)
    • +
    + +

    Changed

    + +
      +
    • Environment::getInlineParsersForCharacter() now returns an empty array (instead of null) when no matching parsers are found
    • +
    • Three utility classes are now marked final: +
        +
      • Html5Entities
      • +
      • LinkParserHelper
      • +
      • UrlEncoder
      • +
      +
    • +
    + +

    Fixed

    + +
      +
    • Improved performance of several methods (for a 10% overall performance boost - #292)
    • +
    + +

    Deprecated

    + +

    The following methods were deprecated and are scheduled for removal in 0.17.0 or 1.0.0 (whichever comes first). See UPGRADE.md for more information.

    + +
      +
    • Cursor::advanceWhileMatches() deprecated; use Cursor::match() instead.
    • +
    • HtmlRenderer::escape() deprecated; use Xml::escape() instead.
    • +
    + +

    Removed

    + +
      +
    • Removed DelimiterStack::findFirstMatchingOpener() which was previously deprecated in 0.15.0
    • +
    + +

    0.15.7 - 2017-10-26

    + +

    Fixed

    + +
      +
    • Improved performance of Cursor::advanceBy() (for a 16% performance boost!) :tada:
    • +
    + +

    0.15.6 - 2017-08-08

    + +

    Fixed

    + +
      +
    • Fixed URI normalization not properly encoding/decoding special characters in certain cases (#287)
    • +
    + +

    0.15.5 - 2017-08-05

    + +

    This release bumps spec compliance to 0.28 without breaking changes to the API.

    + +

    Added

    + +
      +
    • Project is now tested against PHP 7.2
    • +
    + +

    Changed

    + +
      +
    • Bumped CommonMark spec target to 0.28
    • +
    • Changed internal implementation of LinkParserHelper::parseLinkDestination() to allow nested parens
    • +
    • Changed precedence of strong/emph when both nestings are possible (rule 14)
    • +
    • Allow tabs before and after ATX closing header
    • +
    + +

    Fixed

    + +
      +
    • Fixed HTML type 6 block regex matching against <pre> (it shouldn’t) and not matching <iframe> (it should)
    • +
    • Fixed reference parser incorrectly handling escaped ] characters
    • +
    • Fixed “multiple of 3” delimiter run calculations
    • +
    + +

    Deprecated

    + +

    An unused constant and static method were deprecated and will be removed in a future release. See for more information.

    + +
      +
    • Deprecated RegexHelper::REGEX_UNICODE_WHITESPACE (no longer used)
    • +
    • Deprecated RegexHelper::getLinkDestinationRegex() (no longer used)
    • +
    + +

    0.15.4 - 2017-05-09

    + +

    Added

    + +
      +
    • Added new methods to Cursor (#280): +
        +
      • advanceToNextNonSpaceOrNewline() - Identical replacement for the (now-deprecated) advanceToFirstNonSpace() method
      • +
      • advanceToNextNonSpaceOrTab() - Similar replacement for advanceToFirstNonSpace() but with proper tab handling
      • +
      • getNextNonSpaceCharacter() - Identical replacement for the (now-deprecated) getFirstNonSpaceCharacter() method
      • +
      • getNextNonSpacePosition() - Identical replacement for the (now-deprecated) getFirstNonSpacePosition() method
      • +
      +
    • +
    • Added new method to CursorState (#280): +
        +
      • getNextNonSpaceCache() - Identical replacement for the (now-deprecated) getFirstNonSpaceCache() method
      • +
      +
    • +
    + +

    Fixed

    + +
      +
    • Fixed duplicate characters in non-intended lines containing tabs (#279)
    • +
    + +

    Deprecated

    + +

    All deprecations listed here will be removed in a future 0.x release. See UPGRADE.md for instructions on preparing your code for the eventual removal of these methods.

    + +
      +
    • Deprecated Cursor::advanceToFirstNonSpace() (#280) +
        +
      • Use advanceToNextNonSpaceOrTab() or advanceToNextNonSpaceOrNewline() instead, depending on your requirements
      • +
      +
    • +
    • Deprecated Cursor::getFirstNonSpaceCharacter() (#280) +
        +
      • Use Cursor::getNextNonSpaceCharacter() instead
      • +
      +
    • +
    • Deprecated Cursor::getFirstNonSpacePosition() (#280) +
        +
      • Use Cursor::getNextNonSpacePosition() instead
      • +
      +
    • +
    • Deprecated CursorState::getFirstNonSpaceCache() (#280) +
        +
      • Use CursorState::getNextNonSpaceCache() instead
      • +
      +
    • +
    + +

    0.15.3 - 2016-12-19

    + +

    Fixed

    +
      +
    • Allow inline parsers matching regex delimiter to be created (#271, #272)
    • +
    + +

    0.15.2 - 2016-11-22

    + +

    Changed

    +
      +
    • Bumped spec target version to 0.27 (#268)
    • +
    • H2-H6 elements are now parsed as HTML block elements instead of HTML inlines
    • +
    + +

    Fixed

    +
      +
    • Fixed incomplete punctuation regex
    • +
    • Fixed shortcut links not being allowed before a (
    • +
    • Fixed distinction between Unicode whitespace and regular whitespace
    • +
    + +

    0.15.1 - 2016-11-08

    + +

    Fixed

    +
      +
    • Fixed setext heading underlines not allowing trailing tabs (#266)
    • +
    + +

    0.15.0 - 2016-09-14

    + +

    Added

    +
      +
    • Added preliminary support for PHP 7.1 (#259)
    • +
    • Added more regression tests (#258, #260)
    • +
    + +

    Changed

    +
      +
    • Bumped spec target version to 0.26 (#260)
    • +
    • The CursorState constructor requires an additional parameter (#258)
    • +
    • Ordered lists cannot interupt a paragraph unless they start with 1 (#260)
    • +
    • Blank list items cannot interupt a paragraph (#260)
    • +
    + +

    Deprecated

    +
      +
    • Deprecated DelimiterStack::findFirstMatchingOpener() - use findMatchingOpener() instead (#260)
    • +
    + +

    Fixed

    +
      +
    • Fixed tabs in ATX headers and thematic breaks (#260)
    • +
    • Fixed issue where cursor state was not being restored properly (#258, #260) +
        +
      • This fixed the lists-with-tabs regression reported in #258
      • +
      +
    • +
    + +

    Removed

    +
      +
    • Removed an unnecessary check in Cursor::advanceBy() (#260)
    • +
    • Removed the two-blanks-break-out-of-lists feature (#260)
    • +
    + +

    0.14.0 - 2016-07-02

    + +

    Added

    +
      +
    • The safe option is deprecated and replaced by 2 new options (#253, #255): +
        +
      • html_input (strip, allow or escape): how to handle untrusted HTML input (the default is strip for BC reasons)
      • +
      • allow_unsafe_links (true or false): whether to allow risky image URLs and links (the default is true for BC reasons)
      • +
      +
    • +
    + +

    Deprecated

    +
      +
    • The safe option is now deprecated and will be removed in the 1.0.0 release.
    • +
    + +

    0.13.4 - 2016-06-14

    + +

    Fixed

    +
      +
    • Fixed path to autoload.php within bin/commonmark (#250)
    • +
    + +

    0.13.3 - 2016-05-21

    + +

    Added

    +
      +
    • Added setUrl() method for Link and Image elements (#227, #244)
    • +
    • Added cebe/markdown to the benchmark tool (#245)
    • +
    + +

    0.13.2 - 2016-03-27

    + +

    Added

    +
      +
    • Added ability to invoke Converter as a function (#233, #239)
    • +
    • Added new advanceBySpaceOrTab convenience method to Cursor
    • +
    + +

    Changed

    +
      +
    • Bumped spec target version to 0.25
    • +
    • Adjusted how tabs are handled by the Cursor (#234)
    • +
    • Made a couple small micro-optimizations to heavily used functions (#240)
    • +
    • Updated URLs in docblocks to use HTTPS where possible (#238)
    • +
    + +

    0.13.1 - 2016-03-09

    + +

    Changed

    +
      +
    • Refactored EmphasisParser::parse() to simplify it (#223)
    • +
    • Updated dev dependencies (#218 & #220)
    • +
    + +

    Fixed

    +
      +
    • Fixed invalid regex generated when no inline parsers are defined (#224)
    • +
    • Fixed logic bug with blank line after empty list item (#230)
    • +
    • Fixed some incorrect code comments
    • +
    + +

    Removed

    +
      +
    • Removed unused variables (#223)
    • +
    + +

    0.13.0 - 2016-01-14

    + +

    Added

    +
      +
    • Added AST document processors (#210)
    • +
    • Added optional Environment parameter to CommonMarkConverter constructor
    • +
    + +

    Changed

    +
      +
    • Renamed “header” things to “heading” for spec consistency +
        +
      • Header => Heading
      • +
      • ATXHeaderParser => ATXHeadingParser
      • +
      • SetExtHeaderParser => SetExtHeadingParser
      • +
      • HeaderRenderer => HeadingRenderer
      • +
      +
    • +
    • Renamed “HorizontalRule” to “ThematicBreak” for spec consistency +
        +
      • HorizontalRule => ThematicBreak
      • +
      • HorizontalRuleParser => ThematicBreakParser
      • +
      • HorizontalRuleRenderer => ThematicBreakRenderer
      • +
      • HorizontalRuleRendererTest => ThematicBreakRendererTest
      • +
      • RegexHelper::getHRuleRegex() => RegexHelper::getThematicBreakRegex()
      • +
      +
    • +
    • Renamed inline “Html” and “RawHtml” to “HtmlInline” for consistency +
        +
      • Html => HtmlInline
      • +
      • RawHtmlParser => HtmlInlineParser
      • +
      • RawHtmlRenderer => HtmlInlineRenderer
      • +
      +
    • +
    • Don’t allow whitespace between link text and link label of a reference link (spec change)
    • +
    • Don’t allow spaces in link destinations, even in <>
    • +
    • Allow multiline setext header content +
        +
      • The Heading constructor now allows $contents to be a string (old behavior) or string[] (new)
      • +
      +
    • +
    + +

    Fixed

    +
      +
    • Fixed several list issues and regressions (jgm/commonmark.js#59)
    • +
    + +

    Removed

    +
      +
    • Removed schema whitelist from autolink regex
    • +
    • Moved SmartPunct functionality into new league/commonmark-extras package
    • +
    + +

    0.12.0 - 2015-11-04

    + +

    Added

    +
      +
    • Added ability to configure characters and disable emphasis/strong (#135)
    • +
    • Added new ConfigurationAwareInterface support for all parsers, processors, and renderers (#201)
    • +
    • Added HTML safe mode to handle untrusted input (#200, #201) +
        +
      • Safe mode is disabled by default for backwards-compatibility
      • +
      • To enable it, set the safe option to true
      • +
      +
    • +
    • Added AppVeyor integration for automated unit/functional testing on Windows (#195)
    • +
    + +

    Changed

    +
      +
    • AbstractBlock::finalize() now reqires a second parameter, $endLineNumber
    • +
    • RegexHelper::REGEX_ENTITY no longer includes the starting / or the ending /i (#194)
    • +
    • Node::setParent() now accepts null values (#203)
    • +
    + +

    Fixed

    +
      +
    • Fixed incorrect endLine positions (#187)
    • +
    • Fixed DocParser::preProcessInput dropping up to 2 ending newlines instead of just one
    • +
    • Fixed EntityParser not checking for ampersands at the start of the current position (#192, #194)
    • +
    + +

    Removed

    +
      +
    • Removed protected function Context::addChild() +
        +
      • It was a duplicate of the Context::addBlock() method
      • +
      +
    • +
    • Disabled STDIN reading on bin/commonmark for Windows due to PHP issues (#189, #195)
    • +
    + +

    0.11.3 - 2015-09-25

    + +

    Fixed

    +
      +
    • Reset container after closing containing lists (#183; jgm/commonmark.js#67) +
        +
      • The temporary fix from 0.11.2 was reverted
      • +
      +
    • +
    + +

    0.11.2 - 2015-09-23

    + +

    Fixed

    +
      +
    • Fixed parser checking acceptsLines on the wrong element (#183)
    • +
    + +

    0.11.1 - 2015-09-22

    + +

    Changed

    +
      +
    • Tightened up some loose comparisons
    • +
    + +

    Fixed

    +
      +
    • Fixed missing “bin” directive in composer.json
    • +
    • Updated a docblock to match recent changes to method parameters
    • +
    + +

    Removed

    +
      +
    • Removed unused variable from within QuoteProcessor’s closure
    • +
    + +

    0.11.0 - 2015-09-19

    + +

    This release contains several major internal changes. It will likely break compatibility with custom elements, parsers, and renderers. Simple Markdown parsing is unaffected.

    + +

    Added

    +
      +
    • Added new Node class, which both AbstractBlock and AbstractInline extend from (#169)
    • +
    • Added a NodeWalker and NodeWalkerEvent to traverse the AST without using recursion
    • +
    • Added new InlineContainer interface for blocks
    • +
    • Added new getContainer() and getReferenceMap() methods to InlineParserContext
    • +
    • Added iframe to whitelist of HTML block tags (as per spec)
    • +
    • Added ./bin/commonmark for converting Markdown at the command line
    • +
    + +

    Changed

    +
      +
    • Bumped spec target version to 0.22
    • +
    • Revised AST to use a double-linked list (#169)
    • +
    • AbstractBlock and AbstractInline both extend from Node +
        +
      • Sub-classes must implement new isContainer() method
      • +
      +
    • +
    • Other major changes to AbstractBlock: +
        +
      • getParent() is now parent()
      • +
      • setParent() now expects a Node instead of an AbstractBlock
      • +
      • getChildren() is now children()
      • +
      • getLastChild() is now lastChild()
      • +
      • addChild() is now appendChild()
      • +
      +
    • +
    • InlineParserContext is constructed using the container AbstractBlock and the document’s RefereceMap +
        +
      • The constructor will automatically create the Cursor using the container’s string contents
      • +
      +
    • +
    • InlineParserEngine::parse now requires the Node container and the document’s ReferenceMap instead of a ContextInterface and Cursor
    • +
    • Changed Delimiter to reference the actual inline Node instead of the position +
        +
      • The int $pos protected member and constructor arg is now Node $node
      • +
      • Use getInlineNode() and setInlineNode() instead of getPos() and setPos()
      • +
      +
    • +
    • Changed DocParser::processInlines to use a NodeWalker to iterate through inlines +
        +
      • Walker passed as second argument instead of AbstractBlock
      • +
      • Uses a while loop instead of recursion to traverse the AST
      • +
      +
    • +
    • Image and Link now only accept a string as their second argument
    • +
    • Refactored how CloseBracketParser::parse() works internally
    • +
    • CloseBracketParser::createInline no longer accepts label inlines
    • +
    • Disallow list item starting with multiple blank lines (see jgm/CommonMark#332)
    • +
    • Modified AbstractBlock::setLastLineBlank() +
        +
      • Functionality moved to AbstractBlock::shouldLastLineBeBlank() and new DocParser::setAndPropagateLastLineBlank() method
      • +
      • AbstractBlock::setLastLineBlank() is now a setter method for AbstractBlock::$lastLineBlank
      • +
      +
    • +
    • AbstractBlock::handleRemainingContents() is no longer abstract +
        +
      • A default implementation is provided
      • +
      • Removed duplicate code from sub-classes which used the default implementation - they’ll just use the parent method from now on
      • +
      +
    • +
    + +

    Fixed

    +
      +
    • Fixed logic error in calculation of offset (see jgm/commonmark.js@94053a8)
    • +
    • Fixed bug where DocParser checked the wrong method to determine remainder handling behavior
    • +
    • Fixed bug where HorizontalRuleParser failed to advance the cursor beyond the parsed horizontal rule characters
    • +
    • Fixed DocParser not ignoring the final newline of the input (like the reference parser does)
    • +
    + +

    Removed

    +
      +
    • Removed Block\Element\AbstractInlineContainer +
        +
      • Extend AbstractBlock and implement InlineContainer instead
      • +
      • Use child methods instead of getInlines and setInlines
      • +
      +
    • +
    • Removed AbstractBlock::replaceChild() +
        +
      • Call Node::replaceWith() directly the child node instead
      • +
      +
    • +
    • Removed the getInlines() method from InlineParserContext +
        +
      • Add parsed inlines using $inlineContext->getContainer()->appendChild() instead of $inlineContext->getInlines()->add()
      • +
      +
    • +
    • Removed the ContextInterface argument from AbstractInlineParser::parse() and InlineParserEngine::parseCharacter
    • +
    • Removed the first ArrayCollection $inlines argument from InlineProcessorInterface::processInlines()
    • +
    • Removed CloseBracketParser::nullify()
    • +
    • Removed pre from rule 6 of HTML blocks (see jgm/CommonMark#355)
    • +
    + +

    0.10.0 - 2015-07-25

    + +

    Added

    +
      +
    • Added parent references to inline elements (#124)
    • +
    • Added smart punctuation extension (#134)
    • +
    • Added HTML block types
    • +
    • Added indentation caching to the cursor
    • +
    • Added automated code style checks (#133)
    • +
    • Added support for tag attributes in renderers (#101, #165)
    • +
    + +

    Changed

    +
      +
    • Bumped spec target version to 0.21
    • +
    • Revised HTML block parsing to conform to new spec (jgm/commonmark.js@99bd473)
    • +
    • Imposed 9-digit limit on ordered list markers, per spec
    • +
    • Allow non-initial hyphens in html tag names (jgm/CommonMark#239)
    • +
    • Updated list of block tag names
    • +
    • Changed tab/indentation handling to meet the new spec behavior
    • +
    • Modified spec tests to show spaces and tabs in test results
    • +
    • Replaced HtmlRendererInterface with ElementRendererInterface (#141)
    • +
    • Removed the unnecessary trim() and string cast from ListItemRenderer
    • +
    + +

    Fixed

    +
      +
    • Fixed link reference definition edge case (#120)
    • +
    • Allow literal (non-escaping) backslashes in link destinations (#118)
    • +
    • Allow backslash-escaped backslashes in link labels (#119)
    • +
    • Allow link labels up to 999 characters (per the spec)
    • +
    • Properly split on whitespace when determining code block class (jgm/commonmark.js#54)
    • +
    • Fixed code style issues (#132, #133, #151, #152)
    • +
    • Fixed wording for invalid inline exception (#136)
    • +
    + +

    Removed

    +
      +
    • Removed the advance-by-one optimization due to added cursor complexity
    • +
    + +

    0.9.0 - 2015-06-19

    + +

    Added

    +
      +
    • Added public $data array to block elements (#95)
    • +
    • Added isIndented helper method to Cursor
    • +
    • Added a new Converter base class which CommonMarkConverter extends from (#105)
    • +
    + +

    Changed

    +
      +
    • Bumped spec target version to 0.20 (#112)
    • +
    • Renamed ListBlock::$data and ListItem::$data to $listData
    • +
    • Require link labels to contain non-whitespace (jgm/CommonMark#322)
    • +
    • Use U+FFFD for entities resolving to 0 (jgm/CommonMark#323)
    • +
    • Moved IndentedCodeParser::CODE_INDENT_LEVEL to Cursor::INDENT_LEVEL
    • +
    • Changed arrays to short syntax (#116)
    • +
    • Improved efficiency of DelimiterStack iteration (jgm/commonmark.js#43)
    • +
    + +

    Fixed

    +
      +
    • Fixed open block tag followed by newline not being recognized (jgm/CommonMark#324)
    • +
    • Fixed indented lists sometimes being parsed incorrectly (jgm/commonmark.js#42)
    • +
    + +

    0.8.0 - 2015-04-29

    + +

    Added

    +
      +
    • Allow swapping built-in renderers without using their fully qualified names (#84)
    • +
    • Lots of unit tests (for existing code)
    • +
    • Ability to include arbitrary functional tests in addition to spec-based tests
    • +
    + +

    Changed

    +
      +
    • Dropped support for PHP 5.3 (#64 and #76)
    • +
    • Bumped spec target version to 0.19
    • +
    • Made the AbstractInlineContainer be abstract
    • +
    • Moved environment config. logic into separate class
    • +
    + +

    Fixed

    +
      +
    • Fixed underscore emphasis to conform to spec changes (jgm/CommonMark#317)
    • +
    + +

    Removed

    +
      +
    • Removed PHP 5.3 workaround (see commit 5747822)
    • +
    • Removed unused AbstractWebResource::setUrl() method
    • +
    • Removed unnecessary check for hrule when parsing lists (#85)
    • +
    + +

    0.7.2 - 2015-03-08

    + +

    Changed

    +
      +
    • Bumped spec target version to 0.18
    • +
    + +

    Fixed

    +
      +
    • Fixed broken parsing of emphasized text ending with a ‘0’ character (#81)
    • +
    + +

    0.7.1 - 2015-03-01

    + +

    Added

    +
      +
    • All references can now be obtained from the ReferenceMap via listReferences() (#73)
    • +
    • Test against PHP 7.0 (nightly) but allow failures
    • +
    + +

    Changed

    +
      +
    • ListData::$start now defaults to null instead of 0 (#74)
    • +
    • Replace references to HtmlRenderer with new HtmlRendererInterface
    • +
    + +

    Fixed

    +
      +
    • Fixed 0-based ordered lists starting at 1 instead of 0 (#74)
    • +
    • Fixed errors parsing multi-byte characters (#78 and #79)
    • +
    + +

    0.7.0 - 2015-02-17

    + +

    Now with 50% more speed!

    + +

    Added

    +
      +
    • More unit tests to increase code coverage
    • +
    + +

    Changed

    +
      +
    • Enabled the InlineParserEngine to parse several non-special characters at once (performance boost)
    • +
    • NewlineParser no longer attempts to parse spaces; look-behind is used instead (major performance boost)
    • +
    • Moved closeUnmatchedBlocks into its own class
    • +
    • Image and link elements now extend AbstractInlineContainer; label data is stored via $inlineContents instead
    • +
    • Renamed AbstractInlineContainer::$inlineContents and its getter/setter
    • +
    + +

    Removed

    +
      +
    • Removed the InlineCollection class
    • +
    • Removed the unused ArrayCollection::splice() method
    • +
    • Removed impossible-to-reach code in Cursor::advanceToFirstNonSpace
    • +
    • Removed unnecessary test from the InlineParserEngine
    • +
    • Removed unnecessary/unused RegexHelper::getMainRegex() method
    • +
    + +

    0.6.1 - 2015-01-25

    + +

    Changed

    +
      +
    • Bumped spec target version to 0.17
    • +
    • Updated emphasis parsing for underscores to prevent intra-word emphasis
    • +
    • Defered closing of fenced code blocks
    • +
    + +

    0.6.0 - 2015-01-09

    + +

    Added

    +
      +
    • Bulk registration of parsers/renderers via extensions (#45)
    • +
    • Proper UTF-8 support, especially in the Cursor; mbstring extension is now required (#49)
    • +
    • Environment is now configurable; options can be accessed in its parsers/renderers (#56)
    • +
    • Added some unit tests
    • +
    + +

    Changed

    +
      +
    • Bumped spec target version to 0.15 (#50)
    • +
    • Parsers/renderers are now lazy-initialized (#52)
    • +
    • Some private elements are now protected for easier extending, especially on Element classes (#53)
    • +
    • Renderer option names changed from underscore_case to camelCase (#56)
    • +
    • Moved CommonMark parser/render definitions into CommonMarkCoreExtension
    • +
    + +

    Fixed

    +
      +
    • Improved parsing of emphasis around punctuation
    • +
    • Improved regexes for CDATA and HTML comments
    • +
    • Fixed issue with HTML content that is considered false in loose comparisons, like '0' (#55)
    • +
    • Fixed DocParser trying to add empty strings to closed containers (#58)
    • +
    • Fixed incorrect use of a null parameter value in the HtmlElementTest
    • +
    + +

    Removed

    +
      +
    • Removed unused ReferenceDefinition* classes (#51)
    • +
    • Removed UnicodeCaseFolder in favor of mb_strtoupper
    • +
    + +

    0.5.1 - 2014-12-27

    + +

    Fixed

    +
      +
    • Fixed infinite loop and link-in-link-in-image parsing (#37)
    • +
    + +

    Removed

    +
      +
    • Removed hard dependency on mbstring extension; workaround used if not installed (#38)
    • +
    + +

    0.5.0 - 2014-12-24

    + +

    Added

    +
      +
    • Support for custom directives, parsers, and renderers
    • +
    + +

    Changed

    +
      +
    • Major refactoring to de-couple directives from the parser, support custom directive functionality, and reduce complexity
    • +
    • Updated references to stmd.js in README and docblocks
    • +
    • Modified CHANGELOG formatting
    • +
    • Improved travis configuration
    • +
    • Put tests in autoload-dev
    • +
    + +

    Fixed

    +
      +
    • Fixed CommonMarkConverter re-creating object each time new text is converted (#26)
    • +
    + +

    Removed

    +
      +
    • Removed dependency on symfony/options-resolver (fixes #20)
    • +
    + +

    - 2014-12-16

    + +
      +
    • Changed namespace to League\CommonMark
    • +
    • Made compatible with spec version 0.13
    • +
    • Moved delimiter stack functionality into seperate class
    • +
    • Fixed regex which caused HHVM tests to fail
    • +
    • Added some missing copyright info
    • +
    + + + +
    + +

    + Edit this page +

    +
    + + +
    + + + + + + + + diff --git a/security/index.html b/security/index.html new file mode 100644 index 0000000000..8d69596d46 --- /dev/null +++ b/security/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
    +
    +

    league/commonmark

    +

    Markdown done right

    +

    $ composer require league/commonmark

    +
    +
    + +
    +
    +
    +

    Redirecting…

    +
    + Click here if you are not redirected. + +
    +
    +
    +
    + + + + diff --git a/support.css b/support.css new file mode 100644 index 0000000000..d9b63217f6 --- /dev/null +++ b/support.css @@ -0,0 +1,112 @@ +.support-banner-wrapper { + position: fixed; + bottom: 0; + left: 0; + right: 0; + margin: 1rem; +} + +.support-banner { + color: #fff; + background-color: #000; + box-shadow: 0 10px 15px -3px rgba(0,0,0,.1), 0 4px 6px -2px rgba(0,0,0,.05); + border-radius: 0.5rem; + + padding: 0.5rem 1rem; + margin: 0 auto; + width: 100%; + + justify-content: space-between; + -webkit-box-pack: justify; + + align-items: center; + -webkit-box-align: center; + + display: flex; + flex-wrap: wrap; + flex-direction: row; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + + z-index: 100; +} + +@media screen and (min-width: 1200px) { + .support-banner { + max-width: 1192px; + } +} + +.support-banner-left { + width: 0; + margin: 0; + + flex: 1 1 0%; + -webkit-box-flex: 1; +} + +.support-banner-right { + display: flex; + flex-shrink: 0; + width: auto; + margin-top: 0; + + order: 2; + -webkit-box-ordinal-group: 3; + + justify-content: space-between; + -webkit-box-pack: justify; + + align-items: center; + -webkit-box-align: center; +} + +.support-banner-close { + width: 1.5rem; + margin: 0 0 0 0.5rem; + font-size: .875rem; + height: 1.5rem; + + border: none; + border-radius: 9999px; + padding: 0; + line-height: inherit; + color: inherit; + cursor: pointer; + background-color: transparent; + background-image: none; + -webkit-appearance: button; + + overflow: visible; + text-transform: none; + + display: flex; + + justify-content: center; + -webkit-box-pack: center; + + align-items: center; + -webkit-box-align: center; + + order: 3; + -webkit-box-ordinal-group: 4; +} + +.hidden { + display: none; +} + +@media (min-width: 700px) { + .sm\:inline-block { + display: inline-block; + } +} + +@media (min-width: 1060px) { + .lg\:hidden { + display: none; + } + .lg\:inline-block { + display: inline-block; + } +} diff --git a/support/index.html b/support/index.html new file mode 100644 index 0000000000..69705bbd7b --- /dev/null +++ b/support/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
    +
    +

    league/commonmark

    +

    Markdown done right

    +

    $ composer require league/commonmark

    +
    +
    + +
    +
    +
    +

    Redirecting…

    +
    + Click here if you are not redirected. + +
    +
    +
    +
    + + + + diff --git a/upgrading/changelog/index.html b/upgrading/changelog/index.html new file mode 100644 index 0000000000..81d1283703 --- /dev/null +++ b/upgrading/changelog/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
    +
    +

    league/commonmark

    +

    Markdown done right

    +

    $ composer require league/commonmark

    +
    +
    + +
    +
    +
    +

    Redirecting…

    +
    + Click here if you are not redirected. + +
    +
    +
    +
    + + + + diff --git a/upgrading/index.html b/upgrading/index.html new file mode 100644 index 0000000000..9a862a182e --- /dev/null +++ b/upgrading/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
    +
    +

    league/commonmark

    +

    Markdown done right

    +

    $ composer require league/commonmark

    +
    +
    + +
    +
    +
    +

    Redirecting…

    +
    + Click here if you are not redirected. + +
    +
    +
    +
    + + + + diff --git a/xml/index.html b/xml/index.html new file mode 100644 index 0000000000..2416c337e8 --- /dev/null +++ b/xml/index.html @@ -0,0 +1,51 @@ + + + + + + + + + + CommonMark for PHP - Markdown done right + + + + + + + + + + +
    +
    +

    league/commonmark

    +

    Markdown done right

    +

    $ composer require league/commonmark

    +
    +
    + +
    +
    +
    +

    Redirecting…

    +
    + Click here if you are not redirected. + +
    +
    +
    +
    + + + +