Skip to content

Commit

Permalink
Include the verbatim in the AST (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcospassos authored Dec 16, 2023
1 parent 63ea9b2 commit 51a1129
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 21 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,26 @@ const jsx = render(markdown, {
});
```

#### Handling unsupported features

In some cases, you might want to intentionally omit certain features from your
rendered Markdown. For instance, if your platform doesn't support image rendering,
ou can simply return the original source text instead of trying to display the image.

```ts
import {render, unescape} from '@croct/md-lite';

render(markdown, {
// ... other render functions
image: node => unescape(node.source),
});
```

This code snippet will simply return the raw source code of the image node
instead of trying to render it as an image. You can adapt this approach
to handle any other unsupported feature by defining appropriate render
functions and accessing the relevant data from the AST.

## Contributing

Contributions to the package are always welcome!
Expand Down
1 change: 1 addition & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ export type MarkdownNodeType = keyof MarkdownNodeMap;
export type MarkdownNode<T extends MarkdownNodeType = MarkdownNodeType> = {
[K in MarkdownNodeType]: MarkdownNodeMap[K] & {
type: K,
source: string,
}
}[T];
58 changes: 54 additions & 4 deletions src/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ export function parse(markdown: string): MarkdownNode {
return MarkdownParser.parse(markdown);
}

export function unescape(input: string): string {
let text = '';

for (let index = 0; index < input.length; index++) {
const char = input[index];

if (char === '\\' && index + 1 < input.length) {
text += input[++index];

continue;
}

text += char;
}

return text;
}

class MismatchError extends Error {
public constructor() {
super('Mismatched token');
Expand Down Expand Up @@ -34,10 +52,16 @@ class MarkdownParser {
const root: MarkdownNode<'fragment'> = {
type: 'fragment',
children: [],
source: '',
};

const startIndex = this.index;

let text = '';

let paragraphStartIndex = this.index;
let textStartIndex = this.index;

while (!this.done) {
const escapedText = this.parseText('');

Expand All @@ -52,6 +76,8 @@ class MarkdownParser {
}

if (this.matches(...MarkdownParser.NEW_PARAGRAPH)) {
const paragraphEndIndex = this.index;

while (MarkdownParser.NEWLINE.includes(this.current)) {
this.advance();
}
Expand All @@ -63,15 +89,19 @@ class MarkdownParser {
paragraph = {
type: 'paragraph',
children: root.children,
source: '',
};

root.children = [paragraph];
}

paragraph.source = this.getSlice(paragraphStartIndex, paragraphEndIndex);

if (text !== '') {
paragraph.children.push({
type: 'text',
content: text,
source: this.getSlice(textStartIndex, paragraphEndIndex),
});

text = '';
Expand All @@ -80,13 +110,17 @@ class MarkdownParser {
root.children.push({
type: 'paragraph',
children: [],
source: '',
});
}

paragraphStartIndex = this.index;
textStartIndex = this.index;

continue;
}

const {index} = this;
const nodeStartIndex = this.index;

let node: MarkdownNode|null = null;

Expand All @@ -100,7 +134,7 @@ class MarkdownParser {
}

if (node === null) {
this.seek(index);
this.seek(nodeStartIndex);

text += this.current;

Expand All @@ -119,11 +153,14 @@ class MarkdownParser {
parent.children.push({
type: 'text',
content: text,
source: this.getSlice(textStartIndex, nodeStartIndex),
});
}

text = '';

textStartIndex = this.index;

parent.children.push(node);
}

Expand All @@ -137,24 +174,32 @@ class MarkdownParser {
parent.children.push({
type: 'text',
content: text,
source: this.getSlice(textStartIndex, this.index),
});
}

const lastNode = root.children[root.children.length - 1];

if (lastNode?.type === 'paragraph' && lastNode.children.length === 0) {
root.children.pop();
if (lastNode?.type === 'paragraph') {
if (lastNode.children.length === 0) {
root.children.pop();
} else {
lastNode.source = this.getSlice(paragraphStartIndex, this.index);
}
}

if (root.children.length === 1) {
return root.children[0];
}

root.source = this.getSlice(startIndex, this.index);

return root;
}

private parseCurrent(): MarkdownNode|null {
const char = this.lookAhead();
const startIndex = this.index;

switch (char) {
case '*':
Expand All @@ -170,6 +215,7 @@ class MarkdownParser {
return {
type: delimiter.length === 1 ? 'italic' : 'bold',
children: children,
source: this.getSlice(startIndex, this.index),
};
}

Expand All @@ -183,6 +229,7 @@ class MarkdownParser {
return {
type: 'strike',
children: children,
source: this.getSlice(startIndex, this.index),
};
}

Expand All @@ -206,6 +253,7 @@ class MarkdownParser {
return {
type: 'code',
content: content,
source: this.getSlice(startIndex, this.index),
};
}

Expand All @@ -226,6 +274,7 @@ class MarkdownParser {
type: 'image',
src: src,
alt: alt,
source: this.getSlice(startIndex, this.index),
};
}

Expand All @@ -244,6 +293,7 @@ class MarkdownParser {
type: 'link',
href: href,
children: label,
source: this.getSlice(startIndex, this.index),
};
}

Expand Down
Loading

0 comments on commit 51a1129

Please sign in to comment.