@jianwen-lang/parser 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +16 -1
  2. package/dist/core/block/rules/attribute-line.d.ts +13 -0
  3. package/dist/core/block/rules/attribute-line.js +227 -0
  4. package/dist/core/block/rules/code-block.d.ts +2 -0
  5. package/dist/core/block/rules/code-block.js +73 -0
  6. package/dist/core/block/rules/code-fence.d.ts +15 -0
  7. package/dist/core/block/rules/code-fence.js +37 -0
  8. package/dist/core/block/rules/content-title.d.ts +12 -0
  9. package/dist/core/block/rules/content-title.js +70 -0
  10. package/dist/core/block/rules/footnotes.d.ts +9 -0
  11. package/dist/core/block/rules/footnotes.js +105 -0
  12. package/dist/core/block/rules/html.d.ts +7 -0
  13. package/dist/core/block/rules/html.js +48 -0
  14. package/dist/core/block/rules/image.d.ts +9 -0
  15. package/dist/core/block/rules/image.js +78 -0
  16. package/dist/core/block/rules/list.d.ts +3 -0
  17. package/dist/core/block/rules/list.js +275 -0
  18. package/dist/core/block/rules/paragraph.d.ts +6 -0
  19. package/dist/core/block/rules/paragraph.js +55 -0
  20. package/dist/core/block/rules/quote.d.ts +13 -0
  21. package/dist/core/block/rules/quote.js +104 -0
  22. package/dist/core/block/rules/table.d.ts +2 -0
  23. package/dist/core/block/rules/table.js +199 -0
  24. package/dist/core/block/runtime.d.ts +25 -0
  25. package/dist/core/block/runtime.js +116 -0
  26. package/dist/core/block-parser.js +183 -1322
  27. package/dist/html/convert.d.ts +8 -0
  28. package/dist/html/convert.js +22 -0
  29. package/dist/html/render/blocks.d.ts +15 -0
  30. package/dist/html/render/blocks.js +50 -6
  31. package/dist/html/render/html.d.ts +16 -0
  32. package/dist/html/render/html.js +48 -14
  33. package/dist/html/render/utils.d.ts +1 -0
  34. package/package.json +5 -1
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tryParseImageBlock = tryParseImageBlock;
4
+ exports.matchImageBlock = matchImageBlock;
5
+ const location_1 = require("../../location");
6
+ function tryParseImageBlock(ctx) {
7
+ const imageMatch = matchImageBlock(ctx.trimmedContent);
8
+ if (!imageMatch) {
9
+ return null;
10
+ }
11
+ const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
12
+ let block;
13
+ if (ctx.pending.isDisabled) {
14
+ const disabled = {
15
+ type: 'disabledBlock',
16
+ raw: ctx.lineInfo.raw,
17
+ blockAttrs,
18
+ };
19
+ (0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
20
+ block = disabled;
21
+ }
22
+ else {
23
+ const image = {
24
+ type: 'image',
25
+ url: imageMatch.url,
26
+ shape: imageMatch.shape,
27
+ roundedRadius: imageMatch.roundedRadius,
28
+ blockAttrs,
29
+ };
30
+ (0, location_1.setNodeLocation)(image, ctx.lineLocation);
31
+ block = image;
32
+ }
33
+ ctx.commitBlock(block, blockAttrs);
34
+ return ctx.index + 1;
35
+ }
36
+ function matchImageBlock(trimmed) {
37
+ const m = trimmed.match(/^\[([^\]]+)\]\(([^)]+)\)\s*$/);
38
+ if (!m) {
39
+ return undefined;
40
+ }
41
+ const inside = m[1];
42
+ const urlGroup = m[2];
43
+ if (inside === undefined || urlGroup === undefined) {
44
+ return undefined;
45
+ }
46
+ const url = urlGroup.trim();
47
+ if (url.length === 0) {
48
+ return undefined;
49
+ }
50
+ const parts = inside
51
+ .split(',')
52
+ .map((part) => part.trim())
53
+ .filter((part) => part.length > 0);
54
+ if (!parts.includes('img')) {
55
+ return undefined;
56
+ }
57
+ let shape;
58
+ let roundedRadius;
59
+ for (const part of parts) {
60
+ if (part === 'rounded') {
61
+ shape = 'rounded';
62
+ continue;
63
+ }
64
+ if (part === 'square') {
65
+ shape = 'square';
66
+ continue;
67
+ }
68
+ const roundedMatch = part.match(/^rounded=([0-9.]+)$/);
69
+ if (roundedMatch && roundedMatch[1]) {
70
+ const num = parseFloat(roundedMatch[1]);
71
+ if (!isNaN(num) && num > 0) {
72
+ shape = 'rounded';
73
+ roundedRadius = num;
74
+ }
75
+ }
76
+ }
77
+ return { url, shape, roundedRadius };
78
+ }
@@ -0,0 +1,3 @@
1
+ import { BlockRuleContext } from './types';
2
+ export declare function tryParseListBlock(ctx: BlockRuleContext): number | null;
3
+ export declare function isListItemStart(content: string): boolean;
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tryParseListBlock = tryParseListBlock;
4
+ exports.isListItemStart = isListItemStart;
5
+ const location_1 = require("../../location");
6
+ const lexer_1 = require("../../../lexer/lexer");
7
+ const attribute_line_1 = require("./attribute-line");
8
+ const code_fence_1 = require("./code-fence");
9
+ function tryParseListBlock(ctx) {
10
+ const listFirstMatch = matchListItem(ctx.lineInfo.content, ctx.index + 1, ctx.lineInfo.tabCount + 1);
11
+ if (!listFirstMatch) {
12
+ return null;
13
+ }
14
+ const listMatches = [listFirstMatch];
15
+ const rawLines = [ctx.lineInfo.raw];
16
+ let jList = ctx.index + 1;
17
+ while (jList < ctx.lines.length) {
18
+ const nextRaw = ctx.lines[jList];
19
+ if (nextRaw === undefined) {
20
+ jList += 1;
21
+ continue;
22
+ }
23
+ const nextInfo = (0, lexer_1.getLineInfo)(nextRaw);
24
+ const nextTrimmed = nextInfo.content.trim();
25
+ if (nextTrimmed.length === 0) {
26
+ break;
27
+ }
28
+ if ((0, attribute_line_1.isAttributeOnlyLine)(nextTrimmed)) {
29
+ break;
30
+ }
31
+ const nextMatch = matchListItem(nextInfo.content, jList + 1, nextInfo.tabCount + 1);
32
+ if (!nextMatch) {
33
+ const codeFence = (0, code_fence_1.matchCodeFenceStart)(nextTrimmed);
34
+ const attrCodeFence = codeFence ? null : (0, code_fence_1.matchAttributedCodeFence)(nextTrimmed);
35
+ const effectiveFence = codeFence || attrCodeFence;
36
+ if (effectiveFence && listMatches.length > 0) {
37
+ const codeLines = [];
38
+ const fenceIndent = nextInfo.tabCount;
39
+ let kCode = jList + 1;
40
+ let codeClosed = false;
41
+ while (kCode < ctx.lines.length) {
42
+ const codeRaw = ctx.lines[kCode];
43
+ if (codeRaw === undefined) {
44
+ kCode += 1;
45
+ continue;
46
+ }
47
+ const codeInfo = (0, lexer_1.getLineInfo)(codeRaw);
48
+ const codeTrimmed = codeInfo.content.trim();
49
+ if ((0, code_fence_1.isCodeFenceEnd)(codeTrimmed)) {
50
+ codeClosed = true;
51
+ kCode += 1;
52
+ break;
53
+ }
54
+ let codeLine = codeRaw;
55
+ for (let t = 0; t < fenceIndent && codeLine.startsWith('\t'); t++) {
56
+ codeLine = codeLine.slice(1);
57
+ }
58
+ codeLines.push(codeLine);
59
+ kCode += 1;
60
+ }
61
+ if (codeClosed) {
62
+ const isHtmlBlock = attrCodeFence?.isHtml ?? false;
63
+ const codeBlock = {
64
+ type: 'code',
65
+ language: effectiveFence.language,
66
+ value: codeLines.join('\n'),
67
+ htmlLike: isHtmlBlock ? true : undefined,
68
+ };
69
+ (0, location_1.setNodeLocation)(codeBlock, { line: jList + 1, column: nextInfo.tabCount + 1 });
70
+ const lastItem = listMatches[listMatches.length - 1];
71
+ if (lastItem) {
72
+ if (!lastItem.childBlocks) {
73
+ lastItem.childBlocks = [];
74
+ }
75
+ lastItem.childBlocks.push(codeBlock);
76
+ }
77
+ jList = kCode;
78
+ continue;
79
+ }
80
+ }
81
+ break;
82
+ }
83
+ if (nextMatch.kind !== listFirstMatch.kind) {
84
+ break;
85
+ }
86
+ if (nextMatch.indent < listFirstMatch.indent) {
87
+ break;
88
+ }
89
+ listMatches.push(nextMatch);
90
+ rawLines.push(nextInfo.raw);
91
+ jList += 1;
92
+ }
93
+ const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
94
+ let block;
95
+ if (ctx.pending.isDisabled) {
96
+ const disabled = {
97
+ type: 'disabledBlock',
98
+ raw: rawLines.join('\n'),
99
+ blockAttrs,
100
+ };
101
+ (0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
102
+ block = disabled;
103
+ }
104
+ else {
105
+ const { items } = buildListItems(listMatches, 0, listFirstMatch.indent);
106
+ let listKind = listFirstMatch.kind;
107
+ const firstItem = items[0];
108
+ if (firstItem) {
109
+ listKind = firstItem.kind;
110
+ }
111
+ const listBlock = {
112
+ type: 'list',
113
+ kind: listKind,
114
+ orderedStyle: listKind === 'ordered' ? 'decimal' : undefined,
115
+ children: items,
116
+ blockAttrs,
117
+ };
118
+ (0, location_1.setNodeLocation)(listBlock, ctx.lineLocation);
119
+ block = listBlock;
120
+ }
121
+ ctx.commitBlock(block, blockAttrs);
122
+ return jList;
123
+ }
124
+ function isListItemStart(content) {
125
+ return matchListItem(content) !== undefined;
126
+ }
127
+ function matchListItem(content, line, column) {
128
+ const taskMatch = content.match(/^(-+)(\[(.|..)?\])(?:\s+(.*))?$/);
129
+ if (taskMatch) {
130
+ const dashes = taskMatch[1];
131
+ const markerGroup = taskMatch[2];
132
+ const markerInner = taskMatch[3];
133
+ const textGroup = taskMatch[4] || '';
134
+ if (!dashes || !markerGroup) {
135
+ return undefined;
136
+ }
137
+ const indent = dashes.length;
138
+ const taskStatus = mapTaskStatus(markerInner);
139
+ const text = textGroup.trimEnd();
140
+ const textColumn = getListTextColumn(column, taskMatch[0], textGroup);
141
+ return { kind: 'task', indent, taskStatus, text, line, column: textColumn };
142
+ }
143
+ const orderedMatch = content.match(/^(\d+(?:\.\d+)*)\.?\s*(\[(.|..)?\])?(?:\s+(.*))?$/);
144
+ if (orderedMatch) {
145
+ const ordinalGroup = orderedMatch[1];
146
+ const markerGroup = orderedMatch[2];
147
+ const markerInner = orderedMatch[3];
148
+ const textGroup = orderedMatch[4] || '';
149
+ if (!ordinalGroup) {
150
+ return undefined;
151
+ }
152
+ const ordinal = ordinalGroup;
153
+ const indent = ordinal.split('.').length;
154
+ const taskStatus = markerGroup ? mapTaskStatus(markerInner) : undefined;
155
+ const text = textGroup.trimEnd();
156
+ const textColumn = getListTextColumn(column, orderedMatch[0], textGroup);
157
+ return { kind: 'ordered', indent, ordinal, taskStatus, text, line, column: textColumn };
158
+ }
159
+ const foldableMatch = content.match(/^(\++)(\[(.|..)?\])?(?:\s+(.*))?$/);
160
+ if (foldableMatch) {
161
+ const pluses = foldableMatch[1];
162
+ const markerGroup = foldableMatch[2];
163
+ const markerInner = foldableMatch[3];
164
+ const textGroup = foldableMatch[4] || '';
165
+ if (!pluses) {
166
+ return undefined;
167
+ }
168
+ const indent = pluses.length;
169
+ const taskStatus = markerGroup ? mapTaskStatus(markerInner) : undefined;
170
+ const text = textGroup.trimEnd();
171
+ const textColumn = getListTextColumn(column, foldableMatch[0], textGroup);
172
+ return {
173
+ kind: 'foldable',
174
+ indent,
175
+ ordinal: ordinalFromIndent(indent),
176
+ taskStatus,
177
+ text,
178
+ line,
179
+ column: textColumn,
180
+ };
181
+ }
182
+ const bulletMatch = content.match(/^(-+)(?:\s+(.*))?$/);
183
+ if (bulletMatch) {
184
+ const dashes = bulletMatch[1];
185
+ const textGroup = bulletMatch[2] || '';
186
+ if (!dashes) {
187
+ return undefined;
188
+ }
189
+ const indent = dashes.length;
190
+ const text = textGroup.trimEnd();
191
+ const textColumn = getListTextColumn(column, bulletMatch[0], textGroup);
192
+ return { kind: 'bullet', indent, text, line, column: textColumn };
193
+ }
194
+ return undefined;
195
+ }
196
+ function getListTextColumn(baseColumn, matchText, rawText) {
197
+ if (baseColumn === undefined) {
198
+ return undefined;
199
+ }
200
+ const prefixLength = matchText.length - rawText.length;
201
+ return baseColumn + Math.max(0, prefixLength);
202
+ }
203
+ function mapTaskStatus(markerInner) {
204
+ if (!markerInner || markerInner === '') {
205
+ return 'unknown';
206
+ }
207
+ if (markerInner === 'o') {
208
+ return 'in_progress';
209
+ }
210
+ if (markerInner === 'x') {
211
+ return 'not_done';
212
+ }
213
+ if (markerInner === 'v') {
214
+ return 'done';
215
+ }
216
+ return 'unknown';
217
+ }
218
+ function ordinalFromIndent(indent) {
219
+ if (indent <= 0) {
220
+ return '1';
221
+ }
222
+ return Array(indent).fill('1').join('.');
223
+ }
224
+ function buildListItems(matches, startIndex, baseIndent) {
225
+ const items = [];
226
+ let index = startIndex;
227
+ while (index < matches.length) {
228
+ const current = matches[index];
229
+ if (!current) {
230
+ break;
231
+ }
232
+ if (current.indent < baseIndent) {
233
+ break;
234
+ }
235
+ if (current.indent > baseIndent) {
236
+ break;
237
+ }
238
+ const paragraph = {
239
+ type: 'paragraph',
240
+ children: [{ type: 'text', value: current.text }],
241
+ };
242
+ if (current.line !== undefined && current.column !== undefined) {
243
+ (0, location_1.setNodeLocation)(paragraph, {
244
+ line: current.line,
245
+ column: current.column,
246
+ });
247
+ }
248
+ const childrenBlocks = [paragraph];
249
+ if (current.childBlocks && current.childBlocks.length > 0) {
250
+ childrenBlocks.push(...current.childBlocks);
251
+ }
252
+ index += 1;
253
+ const next = matches[index];
254
+ if (next && next.indent > current.indent) {
255
+ const childResult = buildListItems(matches, index, next.indent);
256
+ childrenBlocks.push({
257
+ type: 'list',
258
+ kind: next.kind,
259
+ orderedStyle: next.kind === 'ordered' ? 'decimal' : undefined,
260
+ children: childResult.items,
261
+ });
262
+ index = childResult.nextIndex;
263
+ }
264
+ const item = {
265
+ type: 'listItem',
266
+ kind: current.kind,
267
+ ordinal: current.ordinal,
268
+ taskStatus: current.taskStatus,
269
+ indent: current.indent,
270
+ children: childrenBlocks,
271
+ };
272
+ items.push(item);
273
+ }
274
+ return { items, nextIndex: index };
275
+ }
@@ -0,0 +1,6 @@
1
+ import { BlockRuleContext } from './types';
2
+ interface ParagraphRuleContext extends BlockRuleContext {
3
+ shouldStopParagraph: (content: string, trimmed: string) => boolean;
4
+ }
5
+ export declare function tryParseParagraphBlock(ctx: ParagraphRuleContext): number;
6
+ export {};
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tryParseParagraphBlock = tryParseParagraphBlock;
4
+ const location_1 = require("../../location");
5
+ const lexer_1 = require("../../../lexer/lexer");
6
+ const attribute_line_1 = require("./attribute-line");
7
+ function tryParseParagraphBlock(ctx) {
8
+ const blockRawLines = [ctx.lineInfo.raw];
9
+ const blockTextLines = [ctx.lineInfo.content];
10
+ let j = ctx.index + 1;
11
+ while (j < ctx.lines.length) {
12
+ const nextRaw = ctx.lines[j];
13
+ if (nextRaw === undefined) {
14
+ j += 1;
15
+ continue;
16
+ }
17
+ const nextInfo = (0, lexer_1.getLineInfo)(nextRaw);
18
+ const nextTrimmed = nextInfo.content.trim();
19
+ if (nextTrimmed.length === 0) {
20
+ break;
21
+ }
22
+ if ((0, attribute_line_1.isAttributeOnlyLine)(nextTrimmed)) {
23
+ break;
24
+ }
25
+ if (ctx.shouldStopParagraph(nextInfo.content, nextTrimmed)) {
26
+ break;
27
+ }
28
+ blockRawLines.push(nextInfo.raw);
29
+ blockTextLines.push(nextInfo.content);
30
+ j += 1;
31
+ }
32
+ const blockText = blockTextLines.join('\n');
33
+ const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
34
+ let block;
35
+ if (ctx.pending.isDisabled) {
36
+ const disabled = {
37
+ type: 'disabledBlock',
38
+ raw: blockRawLines.join('\n'),
39
+ blockAttrs,
40
+ };
41
+ (0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
42
+ block = disabled;
43
+ }
44
+ else {
45
+ const paragraph = {
46
+ type: 'paragraph',
47
+ children: [{ type: 'text', value: blockText }],
48
+ blockAttrs,
49
+ };
50
+ (0, location_1.setNodeLocation)(paragraph, ctx.lineLocation);
51
+ block = paragraph;
52
+ }
53
+ ctx.commitBlock(block, blockAttrs, { allowTag: !ctx.pending.isDisabled });
54
+ return j;
55
+ }
@@ -0,0 +1,13 @@
1
+ import { BlockNode } from '../../ast';
2
+ import { ParseError } from '../../errors';
3
+ import { BlockRuleContext } from './types';
4
+ interface QuoteRuleContext extends BlockRuleContext {
5
+ parseBlocks: (source: string, errors: ParseError[]) => BlockNode[];
6
+ }
7
+ interface QuoteMatchResult {
8
+ level: number;
9
+ text: string;
10
+ }
11
+ export declare function tryParseQuoteBlock(ctx: QuoteRuleContext): number | null;
12
+ export declare function matchQuote(content: string): QuoteMatchResult | undefined;
13
+ export {};
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tryParseQuoteBlock = tryParseQuoteBlock;
4
+ exports.matchQuote = matchQuote;
5
+ const location_1 = require("../../location");
6
+ const lexer_1 = require("../../../lexer/lexer");
7
+ function tryParseQuoteBlock(ctx) {
8
+ const quoteMatch = matchQuote(ctx.lineInfo.content);
9
+ if (!quoteMatch) {
10
+ return null;
11
+ }
12
+ const { level } = quoteMatch;
13
+ const innerLines = [];
14
+ const rawLines = [];
15
+ const normalizedFirstLine = normalizeQuoteLine(quoteMatch, level);
16
+ if (normalizedFirstLine !== undefined) {
17
+ innerLines.push(normalizedFirstLine);
18
+ }
19
+ rawLines.push(ctx.lineInfo.raw);
20
+ let jQuote = ctx.index + 1;
21
+ while (jQuote < ctx.lines.length) {
22
+ const nextRaw = ctx.lines[jQuote];
23
+ if (nextRaw === undefined) {
24
+ jQuote += 1;
25
+ continue;
26
+ }
27
+ const nextInfo = (0, lexer_1.getLineInfo)(nextRaw);
28
+ const nextTrimmed = nextInfo.content.trim();
29
+ if (nextTrimmed.length === 0) {
30
+ break;
31
+ }
32
+ const match = matchQuote(nextInfo.content);
33
+ if (!match) {
34
+ break;
35
+ }
36
+ const normalized = normalizeQuoteLine(match, level);
37
+ if (normalized === undefined) {
38
+ break;
39
+ }
40
+ innerLines.push(normalized);
41
+ rawLines.push(nextInfo.raw);
42
+ jQuote += 1;
43
+ }
44
+ const innerSource = innerLines.join('\n');
45
+ const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
46
+ let block;
47
+ if (ctx.pending.isDisabled) {
48
+ const disabled = {
49
+ type: 'disabledBlock',
50
+ raw: rawLines.join('\n'),
51
+ blockAttrs,
52
+ };
53
+ (0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
54
+ block = disabled;
55
+ }
56
+ else {
57
+ const children = ctx.parseBlocks(innerSource, ctx.errors);
58
+ adjustNestedQuoteLevels(children, level);
59
+ const quote = {
60
+ type: 'quote',
61
+ level,
62
+ children,
63
+ blockAttrs,
64
+ };
65
+ (0, location_1.setNodeLocation)(quote, ctx.lineLocation);
66
+ block = quote;
67
+ }
68
+ ctx.commitBlock(block, blockAttrs);
69
+ return jQuote;
70
+ }
71
+ function matchQuote(content) {
72
+ const m = content.match(/^(@+)\s+(.+)$/);
73
+ if (!m) {
74
+ return undefined;
75
+ }
76
+ const atGroup = m[1];
77
+ const textGroup = m[2];
78
+ if (atGroup === undefined || textGroup === undefined) {
79
+ return undefined;
80
+ }
81
+ const level = atGroup.length;
82
+ const text = textGroup.trimEnd();
83
+ return { level, text };
84
+ }
85
+ function normalizeQuoteLine(match, baseLevel) {
86
+ if (match.level < baseLevel) {
87
+ return undefined;
88
+ }
89
+ const relativeLevel = match.level - baseLevel;
90
+ if (relativeLevel === 0) {
91
+ return match.text;
92
+ }
93
+ const nestedMarkers = '@'.repeat(relativeLevel);
94
+ return `${nestedMarkers} ${match.text}`;
95
+ }
96
+ function adjustNestedQuoteLevels(nodes, parentLevel) {
97
+ for (const node of nodes) {
98
+ if (node.type !== 'quote') {
99
+ continue;
100
+ }
101
+ node.level += parentLevel;
102
+ adjustNestedQuoteLevels(node.children, node.level);
103
+ }
104
+ }
@@ -0,0 +1,2 @@
1
+ import { BlockRuleContext } from './types';
2
+ export declare function tryParseTableBlock(ctx: BlockRuleContext): number | null;