@yozora/tokenizer-table 2.0.4 → 2.0.5

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.
@@ -180,7 +180,7 @@ const match = function (api) {
180
180
  for (let c = cells.length; c < columns.length; ++c) {
181
181
  const cell = {
182
182
  nodeType: ast.TableCellType,
183
- position: { start: Object.assign({}, endPoint), end: Object.assign({}, endPoint) },
183
+ position: { start: { ...endPoint }, end: { ...endPoint } },
184
184
  lines: [],
185
185
  };
186
186
  cells.push(cell);
@@ -237,18 +237,17 @@ const uniqueName = '@yozora/tokenizer-table';
237
237
 
238
238
  class TableTokenizer extends coreTokenizer.BaseBlockTokenizer {
239
239
  constructor(props = {}) {
240
- var _a, _b;
241
240
  super({
242
- name: (_a = props.name) !== null && _a !== void 0 ? _a : uniqueName,
243
- priority: (_b = props.priority) !== null && _b !== void 0 ? _b : coreTokenizer.TokenizerPriority.INTERRUPTABLE_BLOCK,
241
+ name: props.name ?? uniqueName,
242
+ priority: props.priority ?? coreTokenizer.TokenizerPriority.INTERRUPTABLE_BLOCK,
244
243
  });
245
- this.match = match;
246
- this.parse = parse;
247
244
  }
245
+ match = match;
246
+ parse = parse;
248
247
  }
249
248
 
250
249
  exports.TableTokenizer = TableTokenizer;
251
250
  exports.TableTokenizerName = uniqueName;
252
- exports["default"] = TableTokenizer;
251
+ exports.default = TableTokenizer;
253
252
  exports.tableMatch = match;
254
253
  exports.tableParse = parse;
@@ -176,7 +176,7 @@ const match = function (api) {
176
176
  for (let c = cells.length; c < columns.length; ++c) {
177
177
  const cell = {
178
178
  nodeType: TableCellType,
179
- position: { start: Object.assign({}, endPoint), end: Object.assign({}, endPoint) },
179
+ position: { start: { ...endPoint }, end: { ...endPoint } },
180
180
  lines: [],
181
181
  };
182
182
  cells.push(cell);
@@ -233,14 +233,13 @@ const uniqueName = '@yozora/tokenizer-table';
233
233
 
234
234
  class TableTokenizer extends BaseBlockTokenizer {
235
235
  constructor(props = {}) {
236
- var _a, _b;
237
236
  super({
238
- name: (_a = props.name) !== null && _a !== void 0 ? _a : uniqueName,
239
- priority: (_b = props.priority) !== null && _b !== void 0 ? _b : TokenizerPriority.INTERRUPTABLE_BLOCK,
237
+ name: props.name ?? uniqueName,
238
+ priority: props.priority ?? TokenizerPriority.INTERRUPTABLE_BLOCK,
240
239
  });
241
- this.match = match;
242
- this.parse = parse;
243
240
  }
241
+ match = match;
242
+ parse = parse;
244
243
  }
245
244
 
246
245
  export { TableTokenizer, uniqueName as TableTokenizerName, TableTokenizer as default, match as tableMatch, parse as tableParse };
@@ -1,8 +1,8 @@
1
1
  import { IPartialYastBlockToken, ITokenizer, IBaseBlockTokenizerProps, IPhrasingContentLine, IMatchBlockHookCreator, IParseBlockHookCreator, BaseBlockTokenizer, IBlockTokenizer } from '@yozora/core-tokenizer';
2
2
  import { TableType, TableColumn, TableRowType, TableCellType, Position, Table } from '@yozora/ast';
3
3
 
4
- declare type T = TableType;
5
- declare type INode = Table;
4
+ type T = TableType;
5
+ type INode = Table;
6
6
  declare const uniqueName = "@yozora/tokenizer-table";
7
7
  interface IToken extends IPartialYastBlockToken<TableType> {
8
8
  /**
@@ -14,8 +14,8 @@ interface IToken extends IPartialYastBlockToken<TableType> {
14
14
  */
15
15
  rows: ITableRowToken[];
16
16
  }
17
- declare type IThis = ITokenizer;
18
- declare type ITokenizerProps = Partial<IBaseBlockTokenizerProps>;
17
+ type IThis = ITokenizer;
18
+ type ITokenizerProps = Partial<IBaseBlockTokenizerProps>;
19
19
  interface ITableRowToken extends IPartialYastBlockToken<TableRowType> {
20
20
  cells: ITableCellToken[];
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yozora/tokenizer-table",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "author": {
5
5
  "name": "guanghechen",
6
6
  "url": "https://github.com/guanghechen/"
@@ -11,33 +11,37 @@
11
11
  "directory": "tokenizers/table"
12
12
  },
13
13
  "homepage": "https://github.com/yozorajs/yozora/tree/release-2.x.x/tokenizers/table",
14
- "main": "lib/cjs/index.js",
15
- "module": "lib/esm/index.js",
16
- "types": "lib/types/index.d.ts",
17
- "source": "src/index.ts",
14
+ "type": "module",
15
+ "exports": {
16
+ "types": "./lib/types/index.d.ts",
17
+ "import": "./lib/esm/index.mjs",
18
+ "require": "./lib/cjs/index.cjs"
19
+ },
20
+ "source": "./src/index.ts",
21
+ "types": "./lib/types/index.d.ts",
22
+ "main": "./lib/cjs/index.cjs",
23
+ "module": "./lib/esm/index.mjs",
18
24
  "license": "MIT",
19
25
  "engines": {
20
26
  "node": ">= 16.0.0"
21
27
  },
22
28
  "files": [
23
29
  "lib/",
24
- "!lib/**/*.js.map",
25
- "!lib/**/*.d.ts.map",
30
+ "src/",
26
31
  "package.json",
27
32
  "CHANGELOG.md",
28
33
  "LICENSE",
29
34
  "README.md"
30
35
  ],
31
36
  "scripts": {
32
- "build": "cross-env NODE_ENV=production rollup -c ../../rollup.config.js",
33
- "prebuild": "rimraf lib/",
37
+ "build": "rimraf lib/ && cross-env NODE_ENV=production rollup -c ../../rollup.config.mjs",
34
38
  "prepublishOnly": "cross-env ROLLUP_SHOULD_SOURCEMAP=false yarn build",
35
- "test": "cross-env TS_NODE_FILES=true jest --config ../../jest.config.js --rootDir ."
39
+ "test": "cross-env TS_NODE_FILES=true NODE_OPTIONS=--experimental-vm-modules jest --config ../../jest.config.mjs --rootDir ."
36
40
  },
37
41
  "dependencies": {
38
- "@yozora/ast": "^2.0.4",
39
- "@yozora/character": "^2.0.4",
40
- "@yozora/core-tokenizer": "^2.0.4"
42
+ "@yozora/ast": "^2.0.5",
43
+ "@yozora/character": "^2.0.5",
44
+ "@yozora/core-tokenizer": "^2.0.5"
41
45
  },
42
- "gitHead": "c980b95254394dcacba0cbb4bea251350b09397c"
46
+ "gitHead": "7ba3bab49fe65cf2f57082c0503af73da9356cf0"
43
47
  }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { match as tableMatch } from './match'
2
+ export { parse as tableParse } from './parse'
3
+ export { TableTokenizer, TableTokenizer as default } from './tokenizer'
4
+ export { uniqueName as TableTokenizerName } from './types'
5
+ export type {
6
+ IThis as ITableHookContext,
7
+ ITableCellToken,
8
+ ITableRowToken,
9
+ IToken as ITableToken,
10
+ ITokenizerProps as ITableTokenizerProps,
11
+ } from './types'
package/src/match.ts ADDED
@@ -0,0 +1,416 @@
1
+ import type { AlignType, Point, TableColumn } from '@yozora/ast'
2
+ import { TableCellType, TableRowType, TableType } from '@yozora/ast'
3
+ import { AsciiCodePoint, isWhitespaceCharacter } from '@yozora/character'
4
+ import type {
5
+ IMatchBlockHookCreator,
6
+ IPhrasingContentLine,
7
+ IResultOfEatAndInterruptPreviousSibling,
8
+ IResultOfEatLazyContinuationText,
9
+ IResultOfEatOpener,
10
+ IYastBlockToken,
11
+ } from '@yozora/core-tokenizer'
12
+ import { calcEndPoint, calcStartPoint } from '@yozora/core-tokenizer'
13
+ import type { ITableCellToken, ITableRowToken, IThis, IToken, T } from './types'
14
+
15
+ /**
16
+ * A table is an arrangement of data with rows and columns, consisting of
17
+ * a single header row, a delimiter row separating the header from the data,
18
+ * and zero or more data rows.
19
+ *
20
+ * Each row consists of cells containing arbitrary text, in which inlines
21
+ * are parsed, separated by pipes (|). A leading and trailing pipe is also
22
+ * recommended for clarity of reading, and if there’s otherwise parsing
23
+ * ambiguity. Spaces between pipes and cell content are trimmed. Block-level
24
+ * elements cannot be inserted in a table.
25
+ *
26
+ * @see https://github.github.com/gfm/#table
27
+ * @see https://github.com/syntax-tree/mdast#tablerow
28
+ * @see https://github.com/syntax-tree/mdast#tablecell
29
+ */
30
+ export const match: IMatchBlockHookCreator<T, IToken, IThis> = function (api) {
31
+ return {
32
+ isContainingBlock: false,
33
+ eatOpener,
34
+ eatAndInterruptPreviousSibling,
35
+ eatLazyContinuationText,
36
+ }
37
+
38
+ function eatOpener(): IResultOfEatOpener<T, IToken> {
39
+ return null
40
+ }
41
+
42
+ function eatAndInterruptPreviousSibling(
43
+ line: Readonly<IPhrasingContentLine>,
44
+ prevSiblingToken: Readonly<IYastBlockToken>,
45
+ ): IResultOfEatAndInterruptPreviousSibling<T, IToken> {
46
+ /**
47
+ * Four spaces is too much
48
+ * @see https://github.github.com/gfm/#example-57
49
+ */
50
+ if (line.countOfPrecedeSpaces >= 4) return null
51
+
52
+ const { nodePoints, endIndex, firstNonWhitespaceIndex } = line
53
+ if (firstNonWhitespaceIndex >= endIndex) return null
54
+
55
+ const columns: TableColumn[] = []
56
+
57
+ /**
58
+ * eat leading optional pipe
59
+ */
60
+ let c = nodePoints[firstNonWhitespaceIndex].codePoint
61
+ let cIndex =
62
+ c === AsciiCodePoint.VERTICAL_SLASH ? firstNonWhitespaceIndex + 1 : firstNonWhitespaceIndex
63
+ for (; cIndex < endIndex; ) {
64
+ for (; cIndex < endIndex; ++cIndex) {
65
+ c = nodePoints[cIndex].codePoint
66
+ if (!isWhitespaceCharacter(c)) break
67
+ }
68
+ if (cIndex >= endIndex) break
69
+
70
+ // eat left optional colon
71
+ let leftColon = false
72
+ if (c === AsciiCodePoint.COLON) {
73
+ leftColon = true
74
+ cIndex += 1
75
+ }
76
+
77
+ let hyphenCount = 0
78
+ for (; cIndex < endIndex; ++cIndex) {
79
+ c = nodePoints[cIndex].codePoint
80
+ if (c !== AsciiCodePoint.MINUS_SIGN) break
81
+ hyphenCount += 1
82
+ }
83
+
84
+ // hyphen must be exist
85
+ if (hyphenCount <= 0) return null
86
+
87
+ // eat right optional colon
88
+ let rightColon = false
89
+ if (cIndex < endIndex && c === AsciiCodePoint.COLON) {
90
+ rightColon = true
91
+ cIndex += 1
92
+ }
93
+
94
+ // eating next pipe
95
+ for (; cIndex < endIndex; ++cIndex) {
96
+ c = nodePoints[cIndex].codePoint
97
+ if (isWhitespaceCharacter(c)) continue
98
+ if (c === AsciiCodePoint.VERTICAL_SLASH) {
99
+ cIndex += 1
100
+ break
101
+ }
102
+
103
+ // There are other non-white space characters
104
+ return null
105
+ }
106
+
107
+ let align: AlignType = null
108
+ if (leftColon && rightColon) align = 'center'
109
+ else if (leftColon) align = 'left'
110
+ else if (rightColon) align = 'right'
111
+ const column: TableColumn = { align }
112
+ columns.push(column)
113
+ }
114
+
115
+ if (columns.length <= 0) return null
116
+
117
+ const lines = api.extractPhrasingLines(prevSiblingToken)
118
+ if (lines == null || lines.length < 1) return null
119
+
120
+ /**
121
+ * The header row must match the delimiter row in the number of cells.
122
+ * If not, a table will not be recognized
123
+ * @see https://github.github.com/gfm/#example-203
124
+ */
125
+ let cellCount = 0,
126
+ hasNonWhitespaceBeforePipe = false
127
+ const previousLine = lines[lines.length - 1]
128
+ for (let pIndex = previousLine.startIndex; pIndex < previousLine.endIndex; ++pIndex) {
129
+ const p = nodePoints[pIndex]
130
+ if (isWhitespaceCharacter(p.codePoint)) continue
131
+
132
+ if (p.codePoint === AsciiCodePoint.VERTICAL_SLASH) {
133
+ if (hasNonWhitespaceBeforePipe || cellCount > 0) cellCount += 1
134
+ hasNonWhitespaceBeforePipe = false
135
+ continue
136
+ }
137
+
138
+ hasNonWhitespaceBeforePipe = true
139
+
140
+ /**
141
+ * Include a pipe in a cell’s content by escaping it,
142
+ * including inside other inline spans
143
+ */
144
+ if (p.codePoint === AsciiCodePoint.BACKSLASH) pIndex += 1
145
+ }
146
+ if (hasNonWhitespaceBeforePipe && columns.length > 1) cellCount += 1
147
+ if (cellCount !== columns.length) return null
148
+
149
+ const row = calcTableRow(previousLine, columns)
150
+ const nextIndex = endIndex
151
+ const token: IToken = {
152
+ nodeType: TableType,
153
+ position: {
154
+ start: calcStartPoint(previousLine.nodePoints, previousLine.startIndex),
155
+ end: calcEndPoint(nodePoints, nextIndex - 1),
156
+ },
157
+ columns,
158
+ rows: [row],
159
+ }
160
+ return {
161
+ token,
162
+ nextIndex,
163
+ remainingSibling: api.rollbackPhrasingLines(
164
+ lines.slice(0, lines.length - 1),
165
+ prevSiblingToken,
166
+ ),
167
+ }
168
+ }
169
+
170
+ function eatLazyContinuationText(
171
+ line: Readonly<IPhrasingContentLine>,
172
+ token: IToken,
173
+ ): IResultOfEatLazyContinuationText {
174
+ if (line.firstNonWhitespaceIndex >= line.endIndex) {
175
+ return { status: 'notMatched' }
176
+ }
177
+
178
+ const row = calcTableRow(line, token.columns)
179
+ if (row == null) return { status: 'notMatched' }
180
+
181
+ token.rows.push(row)
182
+ return { status: 'opening', nextIndex: line.endIndex }
183
+ }
184
+
185
+ // process table row
186
+ function calcTableRow(line: IPhrasingContentLine, columns: TableColumn[]): ITableRowToken {
187
+ const { nodePoints, startIndex, endIndex, firstNonWhitespaceIndex } = line
188
+
189
+ // eat leading pipe
190
+ let p = nodePoints[firstNonWhitespaceIndex]
191
+ let i =
192
+ p.codePoint === AsciiCodePoint.VERTICAL_SLASH
193
+ ? firstNonWhitespaceIndex + 1
194
+ : firstNonWhitespaceIndex
195
+
196
+ // eat table cells
197
+ const cells: ITableCellToken[] = []
198
+ for (; i < endIndex; i += 1) {
199
+ /**
200
+ * Spaces between pipes and cell content are trimmed
201
+ */
202
+ for (; i < endIndex; ++i) {
203
+ p = nodePoints[i]
204
+ if (!isWhitespaceCharacter(p.codePoint)) break
205
+ }
206
+
207
+ // Start point of the table-cell.
208
+ const startPoint: Point =
209
+ i < endIndex ? calcStartPoint(nodePoints, i) : calcEndPoint(nodePoints, endIndex - 1)
210
+
211
+ // Eating cell contents.
212
+ const cellStartIndex = i,
213
+ cellFirstNonWhitespaceIndex = i
214
+ for (; i < endIndex; ++i) {
215
+ p = nodePoints[i]
216
+ /**
217
+ * Include a pipe in a cell’s content by escaping it,
218
+ * including inside other inline spans
219
+ */
220
+ if (p.codePoint === AsciiCodePoint.BACKSLASH) {
221
+ i += 1
222
+ continue
223
+ }
224
+
225
+ // pipe are boundary character
226
+ if (p.codePoint === AsciiCodePoint.VERTICAL_SLASH) break
227
+ }
228
+ let cellEndIndex = i
229
+ for (; cellEndIndex > cellStartIndex; --cellEndIndex) {
230
+ const p = nodePoints[cellEndIndex - 1]
231
+ if (!isWhitespaceCharacter(p.codePoint)) break
232
+ }
233
+
234
+ // End point of the table-cell
235
+ const endPoint: Point = calcEndPoint(nodePoints, i - 1)
236
+
237
+ const lines: IPhrasingContentLine[] =
238
+ cellFirstNonWhitespaceIndex >= cellEndIndex
239
+ ? []
240
+ : [
241
+ {
242
+ nodePoints,
243
+ startIndex: cellStartIndex,
244
+ endIndex: cellEndIndex,
245
+ firstNonWhitespaceIndex: cellFirstNonWhitespaceIndex,
246
+ countOfPrecedeSpaces: cellFirstNonWhitespaceIndex - cellStartIndex,
247
+ },
248
+ ]
249
+
250
+ const cell: ITableCellToken = {
251
+ nodeType: TableCellType,
252
+ position: { start: startPoint, end: endPoint },
253
+ lines: lines,
254
+ }
255
+ cells.push(cell)
256
+
257
+ /**
258
+ * If there are greater, the excess is ignored
259
+ * @see https://github.github.com/gfm/#example-204
260
+ */
261
+ if (cells.length >= columns.length) break
262
+ }
263
+
264
+ // Start point of the table-row
265
+ const startPoint: Point = calcStartPoint(nodePoints, startIndex)
266
+
267
+ // End point of the table-row
268
+ const endPoint: Point = calcEndPoint(nodePoints, endIndex - 1)
269
+
270
+ /**
271
+ * The remainder of the table’s rows may vary in the number of cells.
272
+ * If there are a number of cells fewer than the number of cells in
273
+ * the header row, empty cells are inserted. If there are greater,
274
+ * the excess is ignored
275
+ * @see https://github.github.com/gfm/#example-204
276
+ */
277
+ for (let c = cells.length; c < columns.length; ++c) {
278
+ const cell: ITableCellToken = {
279
+ nodeType: TableCellType,
280
+ position: { start: { ...endPoint }, end: { ...endPoint } },
281
+ lines: [],
282
+ }
283
+ cells.push(cell)
284
+ }
285
+
286
+ const row: ITableRowToken = {
287
+ nodeType: TableRowType,
288
+ position: { start: startPoint, end: endPoint },
289
+ cells,
290
+ }
291
+ return row
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Find delimiter row
297
+ *
298
+ * The delimiter row consists of cells whose only content are
299
+ * hyphens (-), and optionally, a leading or trailing colon (:),
300
+ * or both, to indicate left, right, or center alignment respectively.
301
+ * @see https://github.github.com/gfm/#delimiter-row
302
+ */
303
+ // function calcTableColumn(
304
+ // nodePoints: ReadonlyArray<INodePoint>,
305
+ // currentLine: IPhrasingContentLine,
306
+ // previousLine: IPhrasingContentLine,
307
+ // ): TableColumn[] | null {
308
+ // /**
309
+ // * The previous line of the delimiter line must not be blank line
310
+ // */
311
+ // if (previousLine.firstNonWhitespaceIndex >= previousLine.endIndex) {
312
+ // return null
313
+ // }
314
+
315
+ // /**
316
+ // * Four spaces is too much
317
+ // * @see https://github.github.com/gfm/#example-57
318
+ // */
319
+ // if (currentLine.firstNonWhitespaceIndex - currentLine.startIndex >= 4) return null
320
+
321
+ // const columns: TableColumn[] = []
322
+
323
+ // /**
324
+ // * eat leading optional pipe
325
+ // */
326
+ // let p = nodePoints[currentLine.firstNonWhitespaceIndex]
327
+ // let cIndex =
328
+ // p.codePoint === AsciiCodePoint.VERTICAL_SLASH
329
+ // ? currentLine.firstNonWhitespaceIndex + 1
330
+ // : currentLine.firstNonWhitespaceIndex
331
+
332
+ // for (; cIndex < currentLine.endIndex; ) {
333
+ // for (; cIndex < currentLine.endIndex; ++cIndex) {
334
+ // p = nodePoints[cIndex]
335
+ // if (!isWhitespaceCharacter(p.codePoint)) break
336
+ // }
337
+ // if (cIndex >= currentLine.endIndex) break
338
+
339
+ // // eat left optional colon
340
+ // let leftColon = false
341
+ // if (p.codePoint === AsciiCodePoint.COLON) {
342
+ // leftColon = true
343
+ // cIndex += 1
344
+ // }
345
+
346
+ // let hyphenCount = 0
347
+ // for (; cIndex < currentLine.endIndex; ++cIndex) {
348
+ // p = nodePoints[cIndex]
349
+ // if (p.codePoint !== AsciiCodePoint.MINUS_SIGN) break
350
+ // hyphenCount += 1
351
+ // }
352
+
353
+ // // hyphen must be exist
354
+ // if (hyphenCount <= 0) return null
355
+
356
+ // // eat right optional colon
357
+ // let rightColon = false
358
+ // if (cIndex < currentLine.endIndex && p.codePoint === AsciiCodePoint.COLON) {
359
+ // rightColon = true
360
+ // cIndex += 1
361
+ // }
362
+
363
+ // // eating next pipe
364
+ // for (; cIndex < currentLine.endIndex; ++cIndex) {
365
+ // p = nodePoints[cIndex]
366
+ // if (isWhitespaceCharacter(p.codePoint)) continue
367
+ // if (p.codePoint === AsciiCodePoint.VERTICAL_SLASH) {
368
+ // cIndex += 1
369
+ // break
370
+ // }
371
+
372
+ // // There are other non-white space characters
373
+ // return null
374
+ // }
375
+
376
+ // let align: AlignType = null
377
+ // if (leftColon && rightColon) align = 'center'
378
+ // else if (leftColon) align = 'left'
379
+ // else if (rightColon) align = 'right'
380
+ // const column: TableColumn = { align }
381
+ // columns.push(column)
382
+ // }
383
+
384
+ // if (columns.length <= 0) return null
385
+
386
+ // /**
387
+ // * The header row must match the delimiter row in the number of cells.
388
+ // * If not, a table will not be recognized
389
+ // * @see https://github.github.com/gfm/#example-203
390
+ // */
391
+ // let cellCount = 0,
392
+ // hasNonWhitespaceBeforePipe = false
393
+ // for (let pIndex = previousLine.startIndex; pIndex < previousLine.endIndex; ++pIndex) {
394
+ // const p = nodePoints[pIndex]
395
+ // if (isWhitespaceCharacter(p.codePoint)) continue
396
+
397
+ // if (p.codePoint === AsciiCodePoint.VERTICAL_SLASH) {
398
+ // if (hasNonWhitespaceBeforePipe || cellCount > 0) cellCount += 1
399
+ // hasNonWhitespaceBeforePipe = false
400
+ // continue
401
+ // }
402
+
403
+ // hasNonWhitespaceBeforePipe = true
404
+
405
+ // /**
406
+ // * Include a pipe in a cell’s content by escaping it,
407
+ // * including inside other inline spans
408
+ // */
409
+ // if (p.codePoint === AsciiCodePoint.BACKSLASH) pIndex += 1
410
+ // }
411
+ // if (hasNonWhitespaceBeforePipe && columns.length > 1) cellCount += 1
412
+ // if (cellCount !== columns.length) return null
413
+
414
+ // // Successfully matched to a legal table delimiter line
415
+ // return columns
416
+ // }
package/src/parse.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { Node, TableCell, TableRow } from '@yozora/ast'
2
+ import { TableCellType, TableRowType, TableType } from '@yozora/ast'
3
+ import type { INodePoint } from '@yozora/character'
4
+ import { AsciiCodePoint } from '@yozora/character'
5
+ import type { IParseBlockHookCreator } from '@yozora/core-tokenizer'
6
+ import { mergeAndStripContentLines } from '@yozora/core-tokenizer'
7
+ import type { INode, IThis, IToken, T } from './types'
8
+
9
+ export const parse: IParseBlockHookCreator<T, IToken, INode, IThis> = api => ({
10
+ parse: tokens =>
11
+ tokens.map(token => {
12
+ const tableRows: TableRow[] = token.rows.map((row): TableRow => {
13
+ const tableCells: TableCell[] = row.cells.map((cell): TableCell => {
14
+ /**
15
+ * Include a pipe in a cell’s content by escaping it, including inside
16
+ * other inline spans
17
+ * @see https://github.github.com/gfm/#example-200
18
+ */
19
+ const contents: INodePoint[] = []
20
+ {
21
+ const nodePoints: INodePoint[] = mergeAndStripContentLines(cell.lines)
22
+ for (let i = 0, endIndex = nodePoints.length; i < endIndex; ++i) {
23
+ const p = nodePoints[i]
24
+ if (p.codePoint === AsciiCodePoint.BACKSLASH && i + 1 < endIndex) {
25
+ const q: INodePoint = nodePoints[i + 1]
26
+ if (q.codePoint !== AsciiCodePoint.VERTICAL_SLASH) contents.push(p)
27
+ contents.push(q)
28
+ i += 1
29
+ } else {
30
+ contents.push(p)
31
+ }
32
+ }
33
+ }
34
+
35
+ const children: Node[] = api.processInlines(contents)
36
+ const tableCell: TableCell = api.shouldReservePosition
37
+ ? { type: TableCellType, position: cell.position, children }
38
+ : { type: TableCellType, children }
39
+ return tableCell
40
+ })
41
+
42
+ const tableRow: TableRow = api.shouldReservePosition
43
+ ? { type: TableRowType, position: row.position, children: tableCells }
44
+ : { type: TableRowType, children: tableCells }
45
+ return tableRow
46
+ })
47
+
48
+ const table: INode = api.shouldReservePosition
49
+ ? { type: TableType, position: token.position, columns: token.columns, children: tableRows }
50
+ : { type: TableType, columns: token.columns, children: tableRows }
51
+ return table
52
+ }),
53
+ })
@@ -0,0 +1,33 @@
1
+ import type {
2
+ IBlockTokenizer,
3
+ IMatchBlockHookCreator,
4
+ IParseBlockHookCreator,
5
+ } from '@yozora/core-tokenizer'
6
+ import { BaseBlockTokenizer, TokenizerPriority } from '@yozora/core-tokenizer'
7
+ import { match } from './match'
8
+ import { parse } from './parse'
9
+ import type { INode, IThis, IToken, ITokenizerProps, T } from './types'
10
+ import { uniqueName } from './types'
11
+
12
+ /**
13
+ * Lexical Analyzer for Table, table-row and table-cell.
14
+ * @see https://github.github.com/gfm/#table
15
+ * @see https://github.com/syntax-tree/mdast#tablerow
16
+ * @see https://github.com/syntax-tree/mdast#tablecell
17
+ */
18
+ export class TableTokenizer
19
+ extends BaseBlockTokenizer<T, IToken, INode, IThis>
20
+ implements IBlockTokenizer<T, IToken, INode, IThis>
21
+ {
22
+ /* istanbul ignore next */
23
+ constructor(props: ITokenizerProps = {}) {
24
+ super({
25
+ name: props.name ?? uniqueName,
26
+ priority: props.priority ?? TokenizerPriority.INTERRUPTABLE_BLOCK,
27
+ })
28
+ }
29
+
30
+ public override readonly match: IMatchBlockHookCreator<T, IToken, IThis> = match
31
+
32
+ public override readonly parse: IParseBlockHookCreator<T, IToken, INode, IThis> = parse
33
+ }
package/src/types.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type {
2
+ Position,
3
+ Table,
4
+ TableCellType,
5
+ TableColumn,
6
+ TableRowType,
7
+ TableType,
8
+ } from '@yozora/ast'
9
+ import type {
10
+ IBaseBlockTokenizerProps,
11
+ IPartialYastBlockToken,
12
+ IPhrasingContentLine,
13
+ ITokenizer,
14
+ } from '@yozora/core-tokenizer'
15
+
16
+ export type T = TableType
17
+ export type INode = Table
18
+ export const uniqueName = '@yozora/tokenizer-table'
19
+
20
+ export interface IToken extends IPartialYastBlockToken<TableType> {
21
+ /**
22
+ * Table column configuration items
23
+ */
24
+ columns: TableColumn[]
25
+ /**
26
+ * Table rows
27
+ */
28
+ rows: ITableRowToken[]
29
+ }
30
+
31
+ export type IThis = ITokenizer
32
+
33
+ export type ITokenizerProps = Partial<IBaseBlockTokenizerProps>
34
+
35
+ export interface ITableRowToken extends IPartialYastBlockToken<TableRowType> {
36
+ cells: ITableCellToken[]
37
+ }
38
+
39
+ export interface ITableCellToken extends IPartialYastBlockToken<TableCellType> {
40
+ position: Position
41
+ lines: IPhrasingContentLine[]
42
+ }