@yozora/tokenizer-image 2.1.3 → 2.1.4
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.
- package/package.json +7 -7
- package/src/index.ts +0 -10
- package/src/match.ts +0 -211
- package/src/parse.ts +0 -50
- package/src/tokenizer.ts +0 -32
- package/src/types.ts +0 -45
- package/src/util.ts +0 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yozora/tokenizer-image",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.4",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "guanghechen",
|
|
6
6
|
"url": "https://github.com/guanghechen/"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"lib/",
|
|
30
|
-
"
|
|
30
|
+
"lib/**/*.map",
|
|
31
31
|
"package.json",
|
|
32
32
|
"CHANGELOG.md",
|
|
33
33
|
"LICENSE",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"test": "cross-env TS_NODE_FILES=true NODE_OPTIONS=--experimental-vm-modules jest --config ../../jest.config.mjs --rootDir ."
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@yozora/ast": "^2.1.
|
|
43
|
-
"@yozora/character": "^2.1.
|
|
44
|
-
"@yozora/core-tokenizer": "^2.1.
|
|
45
|
-
"@yozora/tokenizer-link": "^2.1.
|
|
42
|
+
"@yozora/ast": "^2.1.4",
|
|
43
|
+
"@yozora/character": "^2.1.4",
|
|
44
|
+
"@yozora/core-tokenizer": "^2.1.4",
|
|
45
|
+
"@yozora/tokenizer-link": "^2.1.4"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "aa464ed1e3cd84892773a833910cfc53a556bf5f"
|
|
48
48
|
}
|
package/src/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { ImageType } from '@yozora/ast'
|
|
2
|
-
import type { INodePoint } from '@yozora/character'
|
|
3
|
-
import { AsciiCodePoint } from '@yozora/character'
|
|
4
|
-
import type {
|
|
5
|
-
IInlineToken,
|
|
6
|
-
IMatchInlineHookCreator,
|
|
7
|
-
IResultOfIsDelimiterPair,
|
|
8
|
-
IResultOfProcessDelimiterPair,
|
|
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 ''
|
|
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<IInlineToken>,
|
|
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<IInlineToken>,
|
|
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
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
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
|
-
}
|
package/src/tokenizer.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { Image, ImageType } from '@yozora/ast'
|
|
2
|
-
import type { INodeInterval } from '@yozora/character'
|
|
3
|
-
import type {
|
|
4
|
-
IBaseInlineTokenizerProps,
|
|
5
|
-
IPartialInlineToken,
|
|
6
|
-
ITokenDelimiter,
|
|
7
|
-
ITokenizer,
|
|
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 IPartialInlineToken<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 ITokenDelimiter {
|
|
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
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
}
|