@yozora/tokenizer-image 2.0.3 → 2.0.5-alpha.0

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.
@@ -142,19 +142,18 @@ const uniqueName = '@yozora/tokenizer-image';
142
142
 
143
143
  class ImageTokenizer extends coreTokenizer.BaseInlineTokenizer {
144
144
  constructor(props = {}) {
145
- var _a, _b;
146
145
  super({
147
- name: (_a = props.name) !== null && _a !== void 0 ? _a : uniqueName,
148
- priority: (_b = props.priority) !== null && _b !== void 0 ? _b : coreTokenizer.TokenizerPriority.LINKS,
146
+ name: props.name ?? uniqueName,
147
+ priority: props.priority ?? coreTokenizer.TokenizerPriority.LINKS,
149
148
  });
150
- this.match = match;
151
- this.parse = parse;
152
149
  }
150
+ match = match;
151
+ parse = parse;
153
152
  }
154
153
 
155
154
  exports.ImageTokenizer = ImageTokenizer;
156
155
  exports.ImageTokenizerName = uniqueName;
157
156
  exports.calcImageAlt = calcImageAlt;
158
- exports["default"] = ImageTokenizer;
157
+ exports.default = ImageTokenizer;
159
158
  exports.imageMatch = match;
160
159
  exports.imageParse = parse;
@@ -138,14 +138,13 @@ const uniqueName = '@yozora/tokenizer-image';
138
138
 
139
139
  class ImageTokenizer extends BaseInlineTokenizer {
140
140
  constructor(props = {}) {
141
- var _a, _b;
142
141
  super({
143
- name: (_a = props.name) !== null && _a !== void 0 ? _a : uniqueName,
144
- priority: (_b = props.priority) !== null && _b !== void 0 ? _b : TokenizerPriority.LINKS,
142
+ name: props.name ?? uniqueName,
143
+ priority: props.priority ?? TokenizerPriority.LINKS,
145
144
  });
146
- this.match = match;
147
- this.parse = parse;
148
145
  }
146
+ match = match;
147
+ parse = parse;
149
148
  }
150
149
 
151
150
  export { ImageTokenizer, uniqueName as ImageTokenizerName, calcImageAlt, ImageTokenizer as default, match as imageMatch, parse as imageParse };
@@ -10,8 +10,8 @@ import { INodeInterval } from '@yozora/character';
10
10
  */
11
11
  declare function calcImageAlt(nodes: ReadonlyArray<Node>): string;
12
12
 
13
- declare type T = ImageType;
14
- declare type INode = Image;
13
+ type T = ImageType;
14
+ type INode = Image;
15
15
  declare const uniqueName = "@yozora/tokenizer-image";
16
16
  /**
17
17
  * An image token.
@@ -40,8 +40,8 @@ interface IDelimiter extends IYastTokenDelimiter {
40
40
  */
41
41
  titleContent?: INodeInterval;
42
42
  }
43
- declare type IThis = ITokenizer;
44
- declare type ITokenizerProps = Partial<IBaseInlineTokenizerProps>;
43
+ type IThis = ITokenizer;
44
+ type ITokenizerProps = Partial<IBaseInlineTokenizerProps>;
45
45
 
46
46
  /**
47
47
  * Syntax for images is like the syntax for links, with one difference.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yozora/tokenizer-image",
3
- "version": "2.0.3",
3
+ "version": "2.0.5-alpha.0",
4
4
  "author": {
5
5
  "name": "guanghechen",
6
6
  "url": "https://github.com/guanghechen/"
@@ -11,34 +11,38 @@
11
11
  "directory": "tokenizers/image"
12
12
  },
13
13
  "homepage": "https://github.com/yozorajs/yozora/tree/release-2.x.x/tokenizers/image",
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.3",
39
- "@yozora/character": "^2.0.3",
40
- "@yozora/core-tokenizer": "^2.0.3",
41
- "@yozora/tokenizer-link": "^2.0.3"
42
+ "@yozora/ast": "^2.0.5-alpha.0",
43
+ "@yozora/character": "^2.0.5-alpha.0",
44
+ "@yozora/core-tokenizer": "^2.0.5-alpha.0",
45
+ "@yozora/tokenizer-link": "^2.0.5-alpha.0"
42
46
  },
43
- "gitHead": "8cc8f95cfebc8d752bc3272cdd24965f540c130b"
47
+ "gitHead": "8bf941fe4ef82947165b0f3cc123cd493665e13b"
44
48
  }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './util'
2
+ export { match as imageMatch } from './match'
3
+ export { parse as imageParse } from './parse'
4
+ export { ImageTokenizer, ImageTokenizer as default } from './tokenizer'
5
+ export { uniqueName as ImageTokenizerName } from './types'
6
+ export type {
7
+ IThis as IImageHookContext,
8
+ IToken as IImageToken,
9
+ ITokenizerProps as IImageTokenizerProps,
10
+ } from './types'
package/src/match.ts ADDED
@@ -0,0 +1,211 @@
1
+ import { ImageType } from '@yozora/ast'
2
+ import type { INodePoint } from '@yozora/character'
3
+ import { AsciiCodePoint } from '@yozora/character'
4
+ import type {
5
+ IMatchInlineHookCreator,
6
+ IResultOfIsDelimiterPair,
7
+ IResultOfProcessDelimiterPair,
8
+ IYastInlineToken,
9
+ } from '@yozora/core-tokenizer'
10
+ import { eatOptionalWhitespaces, genFindDelimiter } from '@yozora/core-tokenizer'
11
+ import {
12
+ checkBalancedBracketsStatus,
13
+ eatLinkDestination,
14
+ eatLinkTitle,
15
+ } from '@yozora/tokenizer-link'
16
+ import type { IDelimiter, IThis, IToken, T } from './types'
17
+
18
+ /**
19
+ * Syntax for images is like the syntax for links, with one difference.
20
+ * Instead of link text, we have an image description.
21
+ * The rules for this are the same as for link text, except that
22
+ *
23
+ * a) an image description starts with '![' rather than '[', and
24
+ * b) an image description may contain links.
25
+ *
26
+ * An image description has inline elements as its contents. When an image is
27
+ * rendered to HTML, this is standardly used as the image’s alt attribute.
28
+ *
29
+ * ------
30
+ *
31
+ * A 'opener' type delimiter is one of the following forms:
32
+ *
33
+ * - '!['
34
+ *
35
+ * A 'closer' type delimiter is one of the following forms:
36
+ *
37
+ * - '](url)'
38
+ * - '](url "title")'
39
+ * - '](<url>)'
40
+ * - '](<url> "title")'
41
+ *
42
+ * @see https://github.com/syntax-tree/mdast#image
43
+ * @see https://github.github.com/gfm/#images
44
+ */
45
+ export const match: IMatchInlineHookCreator<T, IDelimiter, IToken, IThis> = function (api) {
46
+ return {
47
+ findDelimiter: () => genFindDelimiter<IDelimiter>(_findDelimiter),
48
+ isDelimiterPair,
49
+ processDelimiterPair,
50
+ }
51
+ /**
52
+ * Images can contains Images, so implement an algorithm similar to bracket
53
+ * matching, pushing all opener delimiters onto the stack
54
+ *
55
+ * The rules for this are the same as for link text, except that
56
+ * (a) an image description starts with '![' rather than '[', and
57
+ * (b) an image description may contain links. An image description has
58
+ * inline elements as its contents. When an image is rendered to HTML,
59
+ * this is standardly used as the image’s alt attribute
60
+ *
61
+ * @see https://github.github.com/gfm/#inline-link
62
+ * @see https://github.github.com/gfm/#example-582
63
+ *
64
+ * @override
65
+ * @see IMatchInlineHook
66
+ */
67
+ function _findDelimiter(startIndex: number, endIndex: number): IDelimiter | null {
68
+ const nodePoints: ReadonlyArray<INodePoint> = api.getNodePoints()
69
+ const blockEndIndex = api.getBlockEndIndex()
70
+
71
+ /**
72
+ * FIXME:
73
+ *
74
+ * This is a hack method to fix the situation where a higher priority token
75
+ * is embedded in the delimiter, at this time, ignore the tokens that have
76
+ * been parsed, and continue to match the content until the delimiter meets
77
+ * its own definition or reaches the right boundary of the block content.
78
+ *
79
+ * This algorithm has not been strictly logically verified, but I think it
80
+ * can work well in most cases. After all, it has passed many test cases.
81
+ * @see https://github.github.com/gfm/#example-588
82
+ */
83
+ for (let i = startIndex; i < endIndex; ++i) {
84
+ const c = nodePoints[i].codePoint
85
+ switch (c) {
86
+ case AsciiCodePoint.BACKSLASH:
87
+ i += 1
88
+ break
89
+ case AsciiCodePoint.EXCLAMATION_MARK: {
90
+ if (i + 1 < endIndex && nodePoints[i + 1].codePoint === AsciiCodePoint.OPEN_BRACKET) {
91
+ return {
92
+ type: 'opener',
93
+ startIndex: i,
94
+ endIndex: i + 2,
95
+ }
96
+ }
97
+ break
98
+ }
99
+ /**
100
+ * An inline link consists of a link text followed immediately by a
101
+ * left parenthesis '(', ..., and a right parenthesis ')'
102
+ * @see https://github.github.com/gfm/#inline-link
103
+ */
104
+ case AsciiCodePoint.CLOSE_BRACKET: {
105
+ /**
106
+ * An inline link consists of a link text followed immediately by a
107
+ * left parenthesis '('
108
+ * @see https://github.github.com/gfm/#inline-link
109
+ */
110
+ if (
111
+ i + 1 >= endIndex ||
112
+ nodePoints[i + 1].codePoint !== AsciiCodePoint.OPEN_PARENTHESIS
113
+ ) {
114
+ break
115
+ }
116
+
117
+ // try to match link destination
118
+ const destinationStartIndex = eatOptionalWhitespaces(nodePoints, i + 2, blockEndIndex)
119
+ const destinationEndIndex = eatLinkDestination(
120
+ nodePoints,
121
+ destinationStartIndex,
122
+ blockEndIndex,
123
+ )
124
+ if (destinationEndIndex < 0) break
125
+
126
+ // try to match link title
127
+ const titleStartIndex = eatOptionalWhitespaces(
128
+ nodePoints,
129
+ destinationEndIndex,
130
+ blockEndIndex,
131
+ )
132
+ const titleEndIndex = eatLinkTitle(nodePoints, titleStartIndex, blockEndIndex)
133
+ if (titleEndIndex < 0) break
134
+
135
+ const _startIndex = i
136
+ const _endIndex = eatOptionalWhitespaces(nodePoints, titleEndIndex, blockEndIndex) + 1
137
+ if (
138
+ _endIndex > blockEndIndex ||
139
+ nodePoints[_endIndex - 1].codePoint !== AsciiCodePoint.CLOSE_PARENTHESIS
140
+ ) {
141
+ break
142
+ }
143
+
144
+ /**
145
+ * Both the title and the destination may be omitted
146
+ * @see https://github.github.com/gfm/#example-495
147
+ */
148
+ return {
149
+ type: 'closer',
150
+ startIndex: _startIndex,
151
+ endIndex: _endIndex,
152
+ destinationContent:
153
+ destinationStartIndex < destinationEndIndex
154
+ ? {
155
+ startIndex: destinationStartIndex,
156
+ endIndex: destinationEndIndex,
157
+ }
158
+ : undefined,
159
+ titleContent:
160
+ titleStartIndex < titleEndIndex
161
+ ? { startIndex: titleStartIndex, endIndex: titleEndIndex }
162
+ : undefined,
163
+ }
164
+ }
165
+ }
166
+ }
167
+ return null
168
+ }
169
+
170
+ function isDelimiterPair(
171
+ openerDelimiter: IDelimiter,
172
+ closerDelimiter: IDelimiter,
173
+ internalTokens: ReadonlyArray<IYastInlineToken>,
174
+ ): IResultOfIsDelimiterPair {
175
+ const nodePoints: ReadonlyArray<INodePoint> = api.getNodePoints()
176
+ const balancedBracketsStatus: -1 | 0 | 1 = checkBalancedBracketsStatus(
177
+ openerDelimiter.endIndex,
178
+ closerDelimiter.startIndex,
179
+ internalTokens,
180
+ nodePoints,
181
+ )
182
+ switch (balancedBracketsStatus) {
183
+ case -1:
184
+ return { paired: false, opener: false, closer: true }
185
+ case 0:
186
+ return { paired: true }
187
+ case 1:
188
+ return { paired: false, opener: true, closer: false }
189
+ }
190
+ }
191
+
192
+ function processDelimiterPair(
193
+ openerDelimiter: IDelimiter,
194
+ closerDelimiter: IDelimiter,
195
+ internalTokens: ReadonlyArray<IYastInlineToken>,
196
+ ): IResultOfProcessDelimiterPair<T, IToken, IDelimiter> {
197
+ const token: IToken = {
198
+ nodeType: ImageType,
199
+ startIndex: openerDelimiter.startIndex,
200
+ endIndex: closerDelimiter.endIndex,
201
+ destinationContent: closerDelimiter.destinationContent,
202
+ titleContent: closerDelimiter.titleContent,
203
+ children: api.resolveInternalTokens(
204
+ internalTokens,
205
+ openerDelimiter.endIndex,
206
+ closerDelimiter.startIndex,
207
+ ),
208
+ }
209
+ return { tokens: [token] }
210
+ }
211
+ }
package/src/parse.ts ADDED
@@ -0,0 +1,50 @@
1
+ import type { Node } from '@yozora/ast'
2
+ import { ImageType } from '@yozora/ast'
3
+ import type { INodePoint } from '@yozora/character'
4
+ import { AsciiCodePoint, calcEscapedStringFromNodePoints } from '@yozora/character'
5
+ import type { IParseInlineHookCreator } from '@yozora/core-tokenizer'
6
+ import { encodeLinkDestination } from '@yozora/core-tokenizer'
7
+ import type { INode, IThis, IToken, T } from './types'
8
+ import { calcImageAlt } from './util'
9
+
10
+ export const parse: IParseInlineHookCreator<T, IToken, INode, IThis> = function (api) {
11
+ return {
12
+ parse: tokens =>
13
+ tokens.map(token => {
14
+ const nodePoints: ReadonlyArray<INodePoint> = api.getNodePoints()
15
+
16
+ // calc url
17
+ let url = ''
18
+ if (token.destinationContent != null) {
19
+ let { startIndex, endIndex } = token.destinationContent
20
+ if (nodePoints[startIndex].codePoint === AsciiCodePoint.OPEN_ANGLE) {
21
+ startIndex += 1
22
+ endIndex -= 1
23
+ }
24
+ const destination = calcEscapedStringFromNodePoints(
25
+ nodePoints,
26
+ startIndex,
27
+ endIndex,
28
+ true,
29
+ )
30
+ url = encodeLinkDestination(destination)
31
+ }
32
+
33
+ // calc alt
34
+ const children: Node[] = api.parseInlineTokens(token.children)
35
+ const alt = calcImageAlt(children)
36
+
37
+ // calc title
38
+ let title: string | undefined
39
+ if (token.titleContent != null) {
40
+ const { startIndex, endIndex } = token.titleContent
41
+ title = calcEscapedStringFromNodePoints(nodePoints, startIndex + 1, endIndex - 1)
42
+ }
43
+
44
+ const node: INode = api.shouldReservePosition
45
+ ? { type: ImageType, position: api.calcPosition(token), url, alt, title }
46
+ : { type: ImageType, url, alt, title }
47
+ return node
48
+ }),
49
+ }
50
+ }
@@ -0,0 +1,32 @@
1
+ import type {
2
+ IInlineTokenizer,
3
+ IMatchInlineHookCreator,
4
+ IParseInlineHookCreator,
5
+ } from '@yozora/core-tokenizer'
6
+ import { BaseInlineTokenizer, TokenizerPriority } from '@yozora/core-tokenizer'
7
+ import { match } from './match'
8
+ import { parse } from './parse'
9
+ import type { IDelimiter, INode, IThis, IToken, ITokenizerProps, T } from './types'
10
+ import { uniqueName } from './types'
11
+
12
+ /**
13
+ * Lexical Analyzer for InlineImage.
14
+ * @see https://github.com/syntax-tree/mdast#image
15
+ * @see https://github.github.com/gfm/#images
16
+ */
17
+ export class ImageTokenizer
18
+ extends BaseInlineTokenizer<T, IDelimiter, IToken, INode, IThis>
19
+ implements IInlineTokenizer<T, IDelimiter, IToken, INode, IThis>
20
+ {
21
+ /* istanbul ignore next */
22
+ constructor(props: ITokenizerProps = {}) {
23
+ super({
24
+ name: props.name ?? uniqueName,
25
+ priority: props.priority ?? TokenizerPriority.LINKS,
26
+ })
27
+ }
28
+
29
+ public override readonly match: IMatchInlineHookCreator<T, IDelimiter, IToken, IThis> = match
30
+
31
+ public override readonly parse: IParseInlineHookCreator<T, IToken, INode, IThis> = parse
32
+ }
package/src/types.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type { Image, ImageType } from '@yozora/ast'
2
+ import type { INodeInterval } from '@yozora/character'
3
+ import type {
4
+ IBaseInlineTokenizerProps,
5
+ IPartialYastInlineToken,
6
+ ITokenizer,
7
+ IYastTokenDelimiter,
8
+ } from '@yozora/core-tokenizer'
9
+
10
+ export type T = ImageType
11
+ export type INode = Image
12
+ export const uniqueName = '@yozora/tokenizer-image'
13
+
14
+ /**
15
+ * An image token.
16
+ */
17
+ export interface IToken extends IPartialYastInlineToken<T> {
18
+ /**
19
+ * Link destination interval.
20
+ */
21
+ destinationContent?: INodeInterval
22
+ /**
23
+ * Link title interval.
24
+ */
25
+ titleContent?: INodeInterval
26
+ }
27
+
28
+ export interface IDelimiter extends IYastTokenDelimiter {
29
+ /**
30
+ * IDelimiter type.
31
+ */
32
+ type: 'opener' | 'closer'
33
+ /**
34
+ * link destination
35
+ */
36
+ destinationContent?: INodeInterval
37
+ /**
38
+ * link title
39
+ */
40
+ titleContent?: INodeInterval
41
+ }
42
+
43
+ export type IThis = ITokenizer
44
+
45
+ export type ITokenizerProps = Partial<IBaseInlineTokenizerProps>
package/src/util.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { Alternative, Literal, Node, Parent } from '@yozora/ast'
2
+
3
+ /**
4
+ * calc alt
5
+ * An image description has inline elements as its contents. When an image
6
+ * is rendered to HTML, this is standardly used as the image’s alt attribute
7
+ * @see https://github.github.com/gfm/#example-582
8
+ */
9
+ export function calcImageAlt(nodes: ReadonlyArray<Node>): string {
10
+ return (nodes as ReadonlyArray<Node & Alternative & Literal & Parent>)
11
+ .map((o): string => {
12
+ if (o.value != null) return o.value
13
+ if (o.alt != null) return o.alt
14
+ if (o.children != null) return calcImageAlt(o.children)
15
+ return ''
16
+ })
17
+ .join('')
18
+ }