Project Structure
Now that the custom Markdown tags engine ships inside Docara itself, this page maps the core files that power it, what each piece does, and where you plug in your own tag classes.
Directory tree
Rooted in Docara core at src/CustomTags:
!folders
- src
- CustomTags -- Attrs.php -- BaseTag.php -- CustomTagAdapter.php -- CustomTagInline.php -- CustomTagNode.php -- CustomTagRegistry.php -- CustomTagRenderer.php -- CustomTagsExtension.php -- CustomTagSpec.php -- TagRegistry.php -- UniversalBlockParser.php -- UniversalInlineParser.php
- Interface -- CustomTagInterface.php
- Providers -- ConfiguratorServiceProvider.php -- ... -- Parser.php !endfolders
Namespaces & autoload
- Core classes live under
Simai\Docara\CustomTags\*(plusSimai\Docara\ParserandSimai\Docara\Interface\CustomTagInterface). CustomTagServiceProviderexpects your tag classes underApp\Helpers\CustomTags\and loads them by short class name fromconfig('tags').- Ensure your project composer autoload maps
"App\\": "source/_core"(or your source dir) and runcomposer dump-autoloadafter adding/removing tag classes.
High-level architecture
Markdown source
|
CustomTagsExtension (installs)
|-- UniversalBlockParser --> CustomTagNode (AST)
|-- UniversalInlineParser (reserved for inline forms)
CustomTagRegistry --> CustomTagSpec (per-tag rules)
CustomTagRenderer --> HTML (uses BaseTag::htmlTag/renderer)
Attrs --> parse/merge attributes
Components and responsibilities
Authoring API
- Interface/CustomTagInterface.php
- The formal contract for a tag:
type(),openRegex(),closeRegex(),htmlTag(),baseAttrs(),allowNestingSame(), optionalattrsFilter()andrenderer().
- The formal contract for a tag:
- CommonMark/BaseTag.php
- Default implementation of the interface with sensible behaviors.
- Extend this for new tags instead of implementing the interface from scratch.
- Your project (
App\Helpers\CustomTags\*)- Your tag classes live in your app. Each returns a unique
type()and may override defaults.
- Your tag classes live in your app. Each returns a unique
Registration & discovery
- CommonMark/TagRegistry.php
- Helper/factory that accepts an array of tag instances and produces a read-only registry.
- CommonMark/CustomTagRegistry.php (core service)
- Container-bound service built by
CustomTagServiceProviderfromconfig('tags')and exposed to the CommonMark layer.
- Container-bound service built by
- Config (
config.php)tagsarray lists short class names to register (resolved toApp\Helpers\CustomTags\<Short>).
Parsing layer
- CommonMark/CustomTagSpec.php
- A compiled, immutable spec per tag: type, open/close regex, nesting rules, wrapper tag, base attrs.
- CommonMark/UniversalBlockParser.php
- Line-based block parser that recognizes
!type/!endtypeusing specs from the registry, captures inner Markdown, and buildsCustomTagNode.
- Line-based block parser that recognizes
- CommonMark/UniversalInlineParser.php
- Reserved for inline patterns/shorthands; kept for symmetry and future use.
- CommonMark/CustomTagNode.php
- AST node carrying the tag type, merged attributes (raw), and child nodes (parsed inner Markdown).
- CommonMark/CustomTagAdapter.php
- Bridge that registers specs, parsers, and renderers into the League CommonMark environment.
- CommonMark/CustomTagsExtension.php
- The CommonMark extension entry point; installs the adapter into the environment.
Rendering layer
- CommonMark/CustomTagRenderer.php
- Rendering pipeline for
CustomTagNode:- Parse/normalize inline attributes via Attrs and merge with
baseAttrs(). - Apply per-tag
attrsFilter()if present. - If the tag provides a
renderer(), call it with(innerHtml, attrs). - Otherwise, emit
<htmlTag ...attrs>innerHtml</htmlTag>.
- Parse/normalize inline attributes via Attrs and merge with
- Rendering pipeline for
Utilities
- CommonMark/Attrs.php
- Robust attribute parsing for the open line:
- Key-value pairs:
key="value",key:'value', unquoted tokens. - Shorthands:
.class(append),#id(set). - Unicode spaces/smart quotes are normalized; classes are concatenated and de-duplicated.
- Key-value pairs:
- Attribute set merging with class de-duplication.
- Robust attribute parsing for the open line:
Parser integration
- Parser.php
- Core replacement for Jigsaw's
FrontMatterParser. - Builds the CommonMark environment and installs
CustomTagsExtensionso tags work duringbuild/serve.
- Core replacement for Jigsaw's
File interaction (lifecycle)
Parserbuilds CommonMark environment and installsCustomTagsExtension.CustomTagsExtensionusesCustomTagAdapterto register:UniversalBlockParser,UniversalInlineParser, and a renderer forCustomTagNode.
CustomTagRegistrysuppliesCustomTagSpecinstances derived from registered tag classes.- Parsing:
UniversalBlockParsermatches open/close lines, constructsCustomTagNodewith raw attrs and child nodes.
- Rendering:
CustomTagRenderermerges attributes (Attrs) and renders via wrapper or per-tagrenderer().
Where to add things
- New tag ->
App\Helpers\CustomTags/YourTag.php(extendBaseTag), add toconfig('tags'). - New parsing behavior ->
UniversalBlockParser/UniversalInlineParser. - Custom rendering logic for a specific tag -> override
renderer()in your tag class. - Global attribute rules -> extend logic in
Attrs.
Conventions
- One class per file; class basename matches filename.
type()must be globally unique across all tags.- Keep
baseAttrs()minimal/semantic; let authors add presentation in Markdown. - Avoid HTML injection: escape values in custom
renderer()implementations.
Troubleshooting pointers
- Tag not recognized: Check
config('tags'), namespace, and runcomposer dump-autoload. - Attributes missing: Confirm they're on the open line; check
Attrsnormalization for quotes/spaces. - Wrong wrapper: Verify
htmlTag()override; if usingrenderer(), remember it bypasses the default wrapper. - Nesting issues: Adjust
allowNestingSame()in the tag class.