Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/.phpunit.cache/
/.phpunit.result.cache
/.php-cs-fixer.cache
/var/
/coverage/
/coverage.xml
/clover.xml
30 changes: 30 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Contributing to Alto Markdown

Contributions should preserve the public contracts, source bytes outside the
requested change, and safe defaults.

## Prepare a checkout

```bash
composer install
composer qa
```

`composer qa` runs PHPStan, the PHP CS Fixer check, and PHPUnit. Run coverage
separately when a change affects executable code:

```bash
composer coverage
```

The coverage command enforces the repository's 99 percent line floor.

## Propose a change

Add or update tests for observable behavior. Update `docs/` and `CHANGELOG.md`
when the public contract changes. Keep parser limits, HTML policies, resource
authority, and file-conflict behavior explicit.

Open a pull request against `main` only after the complete quality gate passes.
Describe the user-visible result, compatibility impact, and any security or
performance tradeoff.
8 changes: 8 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Security policy

Do not report a suspected vulnerability through a public GitHub issue. Email
[security@altocoda.com](mailto:security@altocoda.com) with the affected
version, impact, reproduction, and any known mitigation.

Reports are accepted in English or French and handled through coordinated
disclosure. Avoid including secrets or unrelated personal data.
9 changes: 9 additions & 0 deletions SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Support

Use GitHub issues for reproducible bugs and focused feature proposals. Include
the PHP version, package version, selected profile, minimal Markdown input,
expected result, and actual result.

Usage questions should include the smallest complete code example and explain
the application boundary involved. Do not post security vulnerabilities in a
public issue; follow the private process in `SECURITY.md`.
96 changes: 96 additions & 0 deletions docs/extensions/attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Attributes

`AttributesExtension` adds constrained HTML attributes to Markdown elements.
Attribute names are explicitly allowed by `AttributesPolicy` before source
content can use them.

## Install

This extension is bundled with `alto/markdown`:

```bash
composer require alto/markdown
```

## Configure

Allow the attributes required by the application:

```php
use Alto\Markdown\Extension\Attributes\AttributesExtension;
use Alto\Markdown\Extension\Attributes\AttributesPolicy;
use Alto\Markdown\Markdown;

$markdown = Markdown::github()->with(
new AttributesExtension(new AttributesPolicy([
'id',
'class',
'title',
])),
);
```

## Markdown

```markdown
{#intro .lead title="Welcome home"}
# Hello
```

## HTML

```html
<h1 class="lead" id="intro" title="Welcome home">Hello</h1>
```

## Options

Without an explicit policy, only `id` and `class` are allowed.

| Option | Default | Purpose |
| --- | ---: | --- |
| `allowed` | `['id', 'class']` | HTML attribute names accepted from Markdown |
| `maxAttributes` | `32` | Maximum attributes in one list |
| `maxListBytes` | `1024` | Maximum byte length of one attribute list |
| `maxValueBytes` | `512` | Maximum byte length of one value |

Event handler names such as `onclick` cannot be allowed. Attribute names are
matched case-insensitively.

## Security

The policy is an allowlist for attributes authored in Markdown. Event handler
names are always rejected, and configured length limits bound each parsed
attribute list.

URL attributes remain subject to the active HTML policy. Allow only attributes
the application intends to expose to content authors.

## Behavior

A block attribute list before a block decorates the next sibling. Prefix a
list with `:` to decorate the previous sibling instead:

```markdown
> Quoted text
> {: .quoted}
```

```html
<blockquote>
<p class="quoted">Quoted text</p>
</blockquote>
```

An inline list decorates only the rendered element immediately before it:

```markdown
Read the *important*{.accent} note.
```

```html
<p>Read the <em class="accent">important</em> note.</p>
```

Attributes native to the Markdown element take precedence. Classes from the
element and the attribute list are merged without duplicates.
77 changes: 77 additions & 0 deletions docs/extensions/code-block-titles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Code block titles

`CodeBlockTitleExtension` reads a title from a fenced code block's info string.
It wraps the code block in a figure with a visible caption.

## Install

This extension is bundled with `alto/markdown`:

```bash
composer require alto/markdown
```

## Configure

Add the extension to any factory profile:

```php
use Alto\Markdown\Extension\CodeBlockTitle\CodeBlockTitleExtension;
use Alto\Markdown\Markdown;

$markdown = Markdown::github()->with(new CodeBlockTitleExtension());
```

## Markdown

````markdown
```php title="src/App.php"
<?php echo 1;
```
````

## HTML

```html
<figure class="code-block has-title" data-title="src/App.php">
<figcaption class="code-title">src/App.php</figcaption>
<pre><code class="language-php">&lt;?php echo 1;
</code></pre>
</figure>
```

## Options

Pass a `CodeBlockTitlePolicy` to change the wrapper or parser limits:

```php
use Alto\Markdown\Extension\CodeBlockTitle\CodeBlockTitlePolicy;

$extension = new CodeBlockTitleExtension(new CodeBlockTitlePolicy(
figureClass: 'code-block',
captionClass: 'code-block-title',
includeDataTitle: false,
));
```

| Option | Default | Purpose |
| --- | --- | --- |
| `figureClass` | `code-block has-title` | Classes added to the figure |
| `captionClass` | `code-title` | Class added to the caption |
| `includeDataTitle` | `true` | Add the title as `data-title` on the figure |
| `maxInfoBytes` | `4096` | Maximum fenced-code info string length |
| `maxTitleBytes` | `512` | Maximum decoded title length |

## Security

The title and code content are escaped before insertion into HTML. Parser
limits bound the info string and decoded title. The extension performs no I/O
and does not enable raw HTML.

## Behavior

Both quoted and unquoted values are accepted. `filename` is an alias when no
`title` is present. If both appear, `title` takes precedence.

A missing, malformed, or oversized title leaves the ordinary `<pre><code>`
output unchanged.
75 changes: 75 additions & 0 deletions docs/extensions/content-slicer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Content slicer

`ContentSlicerExtension` groups root-level heading sections in semantic
`<section>` elements. It changes HTML structure without changing the Markdown
document.

## Install

This extension is bundled with `alto/markdown`:

```bash
composer require alto/markdown
```

## Configure

Add the extension to any factory profile:

```php
use Alto\Markdown\Extension\ContentSlicer\ContentSlicerExtension;
use Alto\Markdown\Markdown;

$markdown = Markdown::github()->with(new ContentSlicerExtension());
```

## Markdown

```markdown
# Main

Intro.

## Install

Run Composer.
```

## HTML

```html
<h1>Main</h1>
<p>Intro.</p>
<section>
<h2>Install</h2>
<p>Run Composer.</p>
</section>
```

## Options

The constructor accepts the first heading level that opens a section:

```php
$markdown = Markdown::github()->with(
new ContentSlicerExtension(minLevel: 3),
);
```

`minLevel` defaults to `2` and must be between `1` and `6`. Deeper matching
headings create nested sections.

## Security

The extension introduces no source syntax, resource access, or raw HTML. It
wraps existing rendered blocks in generated `<section>` elements. The active
HTML policy still controls the final fragment.

## Behavior

Only headings in the document root participate in the outline. Headings
inside block quotes and lists keep their normal HTML without opening sections.

The transformation is render-only. `toMarkdown()` preserves the original
source. The curated HTML policy preserves the content but unwraps `<section>`
elements that are outside its allowed HTML subset.
73 changes: 73 additions & 0 deletions docs/extensions/default-attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Default attributes

`DefaultAttributesExtension` adds application-defined HTML attributes to
native Markdown elements. It does not add Markdown syntax.

## Install

This extension is bundled with `alto/markdown`:

```bash
composer require alto/markdown
```

## Configure

Map node kinds to the attributes they should receive:

```php
use Alto\Markdown\Extension\DefaultAttributes\DefaultAttributesExtension;
use Alto\Markdown\Markdown;

$markdown = Markdown::github()->with(
new DefaultAttributesExtension([
'paragraph' => [
'class' => ['prose', 'content'],
'data-kind' => 'body',
],
'link' => [
'class' => 'link',
'target' => '_self',
],
]),
);
```

## Markdown

```markdown
Read [Alto](https://altophp.com).
```

## HTML

```html
<p class="prose content" data-kind="body">Read <a href="https://altophp.com" class="link" target="_self">Alto</a>.</p>
```

## Options

Each attribute value is a string or boolean. The `class` attribute also
accepts a list of strings. Duplicate classes are removed, `true` emits a
valueless attribute, and `false` omits it.

Supported core kinds are `paragraph`, `atx-heading`, `setext-heading`,
`indented-code`, `fenced-code`, `block-quote`, `list`, `list-item`,
`thematic-break`, `hard-break`, `code-span`, `emphasis`, `strong`, `link`,
`image`, and `autolink`. The `strikethrough` and `gfm:table` kinds require the
GFM or GitHub profile. The `github:alert` kind requires the GitHub profile.

## Security

Configuration is trusted application code, not an allowlist for untrusted
Markdown. It may add attributes such as `style` or event handlers. URL
attributes still follow the active HTML policy.

Use the curated HTML policy when the final fragment must remove attributes
outside its allowlist.

## Behavior

Attributes produced by the Markdown element take precedence over configured
defaults. Default classes are added first and merged with native or authored
classes.
Loading
Loading