@peaceroad/markdown-imgattr-to-pcaption 0.1.0 → 0.2.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.
- package/README.md +66 -4
- package/index.js +279 -127
- package/package.json +10 -2
- package/test/examples-img-title-attr.txt +0 -87
- package/test/examples-label-lang.txt +0 -16
- package/test/examples.txt +0 -88
- package/test/test.js +0 -77
package/README.md
CHANGED
|
@@ -64,7 +64,9 @@ setMarkdownImgAttrToPCaption(markdownCont)
|
|
|
64
64
|
|
|
65
65
|
## Option
|
|
66
66
|
|
|
67
|
-
### imgTitleCaption
|
|
67
|
+
### imgTitleCaption
|
|
68
|
+
|
|
69
|
+
Default: false.
|
|
68
70
|
|
|
69
71
|
```
|
|
70
72
|
[Input]
|
|
@@ -85,7 +87,9 @@ setMarkdownImgAttrToPCaption(markdownCont)
|
|
|
85
87
|
段落。段落。段落。
|
|
86
88
|
```
|
|
87
89
|
|
|
88
|
-
### labelLang
|
|
90
|
+
### labelLang
|
|
91
|
+
|
|
92
|
+
Default: 'en'.
|
|
89
93
|
|
|
90
94
|
```
|
|
91
95
|
[Input]
|
|
@@ -99,9 +103,67 @@ setMarkdownImgAttrToPCaption(markdownCont)
|
|
|
99
103
|
[Output]
|
|
100
104
|
段落。段落。段落。
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
図 キャプション
|
|
103
107
|
|
|
104
108
|

|
|
105
109
|
|
|
106
110
|
段落。段落。段落。
|
|
107
|
-
```
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### autoLangDetection
|
|
114
|
+
|
|
115
|
+
Default: true. To force a specific `labelLang`, set `autoLangDetection: false`.
|
|
116
|
+
When `autoLangDetection` is true, it can override an explicitly set `labelLang` (it is treated as the fallback when detection cannot decide).
|
|
117
|
+
|
|
118
|
+
Detect `labelLang` from the first image caption line. If the caption text contains Japanese characters, it sets `labelLang: 'ja'`. Otherwise, if the caption contains ASCII letters, it sets `labelLang: 'en'` (symbols/emoji are ignored). If the caption contains non-ASCII letters such as accents, the existing `labelLang` is left unchanged.
|
|
119
|
+
|
|
120
|
+
Example (non-ASCII letters keep the current `labelLang`):
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
[Input]
|
|
124
|
+
段落。
|
|
125
|
+
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
段落。
|
|
129
|
+
|
|
130
|
+
[Output]
|
|
131
|
+
段落。
|
|
132
|
+
|
|
133
|
+
Figure. Café
|
|
134
|
+
|
|
135
|
+

|
|
136
|
+
|
|
137
|
+
段落。
|
|
138
|
+
```
|
|
139
|
+
Only `ja` and `en` are auto-detected. For other languages, set `labelLang` explicitly (and use `labelSet` as needed) or leave auto-detection off.
|
|
140
|
+
Detection runs only once on the first eligible image line; subsequent images do not affect the language choice.
|
|
141
|
+
|
|
142
|
+
### labelSet
|
|
143
|
+
|
|
144
|
+
Override the auto-inserted label, joint, and space for captions without labels (useful for other languages).
|
|
145
|
+
`labelSet` accepts either a single config for the current `labelLang` or a per-language map.
|
|
146
|
+
If a matching language entry is not found, the default (English) label config is used.
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
setMarkdownImgAttrToPCaption(markdownCont, {
|
|
150
|
+
labelSet: { label: '図', joint: ':', space: ' ' }
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
setMarkdownImgAttrToPCaption(markdownCont, {
|
|
156
|
+
labelSet: {
|
|
157
|
+
en: { label: 'Figure', joint: '.', space: ' ' },
|
|
158
|
+
ja: { label: '図', joint: ' ', space: '' },
|
|
159
|
+
fr: { label: 'Fig', joint: '.', space: ' ' },
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Notes
|
|
165
|
+
|
|
166
|
+
- Only converts images that are on a single line and surrounded by blank lines. Inline images or list items are not changed.
|
|
167
|
+
- Skips fenced code blocks (``` or ~~~).
|
|
168
|
+
- Label detection uses `p7d-markdown-it-p-captions` label patterns (en/ja by default). `labelSet` only affects auto-inserted labels when no label is detected.
|
|
169
|
+
- `autoLangDetection` inspects the first eligible image line and uses the caption text (title when `imgTitleCaption: true`, otherwise alt). If the caption text is empty, it falls back to alt.
|
package/index.js
CHANGED
|
@@ -1,139 +1,305 @@
|
|
|
1
|
+
import { markAfterNum, markReg, joint } from 'p7d-markdown-it-p-captions'
|
|
2
|
+
import langSets from 'p7d-markdown-it-p-captions/lang.js'
|
|
3
|
+
|
|
4
|
+
const imageLineReg = /^([ \t]*?)!\[ *?(.*?) *?\]\(([^ ]*?)( +"(.*?)")?\)( *(?:{.*?})?)$/
|
|
5
|
+
const backquoteFenceReg = /^[ \t]*```/
|
|
6
|
+
const tildeFenceReg = /^[ \t]*~~~/
|
|
7
|
+
const blankLineReg = /^[ \t]*$/
|
|
8
|
+
const asciiOnlyReg = /^[\x00-\x7F]*$/
|
|
9
|
+
const whitespaceOnlyReg = /^[\s\u3000]+$/
|
|
10
|
+
const unicodeLetterReg = (() => {
|
|
11
|
+
try {
|
|
12
|
+
return new RegExp('\\p{L}', 'u')
|
|
13
|
+
} catch {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
})()
|
|
17
|
+
|
|
18
|
+
const DEFAULT_LABEL_CONFIG_MAP = {
|
|
19
|
+
en: { label: 'Figure', joint: '.', space: ' ' },
|
|
20
|
+
ja: { label: '図', joint: ' ', space: '' },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const buildLabelOnlyReg = () => {
|
|
24
|
+
const langs = Object.keys(langSets)
|
|
25
|
+
if (langs.length === 0) return null
|
|
26
|
+
|
|
27
|
+
const patterns = []
|
|
28
|
+
for (const lang of langs) {
|
|
29
|
+
const data = langSets[lang]
|
|
30
|
+
if (!data || !data.markReg || !data.markReg.img) continue
|
|
31
|
+
let pattern = data.markReg.img
|
|
32
|
+
if (data.type && data.type['inter-word-space']) {
|
|
33
|
+
pattern = pattern.replace(/([a-z])/g, (match) => '[' + match + match.toUpperCase() + ']')
|
|
34
|
+
}
|
|
35
|
+
patterns.push(pattern)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (patterns.length === 0) return null
|
|
39
|
+
return new RegExp('^(' + patterns.join('|') + ')([ .]?' + markAfterNum + ')?$')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const labelOnlyReg = buildLabelOnlyReg()
|
|
43
|
+
const jointSuffixReg = new RegExp(joint + '$')
|
|
44
|
+
|
|
45
|
+
const getCaptionText = (imgLine, opt) => {
|
|
46
|
+
if (opt.imgTitleCaption) {
|
|
47
|
+
return imgLine[5] || ''
|
|
48
|
+
}
|
|
49
|
+
return imgLine[2] || ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getCaptionTextForDetection = (imgLine, opt) => {
|
|
53
|
+
const captionText = getCaptionText(imgLine, opt)
|
|
54
|
+
if (captionText) {
|
|
55
|
+
return captionText
|
|
56
|
+
}
|
|
57
|
+
return imgLine[2] || ''
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isAsciiOnly = (value) => asciiOnlyReg.test(value)
|
|
61
|
+
const isAsciiLetterCode = (code) => (
|
|
62
|
+
(code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const isJapaneseCodePoint = (code) => {
|
|
66
|
+
return (
|
|
67
|
+
(code >= 0x3040 && code <= 0x30ff) || // Hiragana + Katakana
|
|
68
|
+
(code >= 0x31f0 && code <= 0x31ff) || // Katakana extensions
|
|
69
|
+
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs
|
|
70
|
+
(code >= 0xff66 && code <= 0xff9f) // Half-width Katakana
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const containsJapanese = (value) => {
|
|
75
|
+
for (const ch of value) {
|
|
76
|
+
const code = ch.codePointAt(0)
|
|
77
|
+
if (code !== undefined && isJapaneseCodePoint(code)) {
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const detectAutoLang = (value) => {
|
|
85
|
+
let hasAsciiLetter = false
|
|
86
|
+
for (const ch of value) {
|
|
87
|
+
const code = ch.codePointAt(0)
|
|
88
|
+
if (code === undefined) continue
|
|
89
|
+
if (isJapaneseCodePoint(code)) return 'ja'
|
|
90
|
+
if (code <= 0x7f) {
|
|
91
|
+
if (isAsciiLetterCode(code)) {
|
|
92
|
+
hasAsciiLetter = true
|
|
93
|
+
}
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (unicodeLetterReg) {
|
|
97
|
+
if (unicodeLetterReg.test(ch)) return null
|
|
98
|
+
} else if (ch.toLowerCase() !== ch.toUpperCase()) {
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return hasAsciiLetter ? 'en' : null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const getInterWordSpace = (labelLang, labelText) => {
|
|
106
|
+
const lang = langSets[labelLang]
|
|
107
|
+
if (lang && lang.type) {
|
|
108
|
+
return Boolean(lang.type['inter-word-space'])
|
|
109
|
+
}
|
|
110
|
+
return isAsciiOnly(labelText)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const normalizeLabelConfig = (value) => {
|
|
114
|
+
if (!value || typeof value !== 'object') return null
|
|
115
|
+
const config = {}
|
|
116
|
+
const labelValue = (typeof value.label === 'string')
|
|
117
|
+
? value.label
|
|
118
|
+
: (typeof value.lable === 'string' ? value.lable : undefined)
|
|
119
|
+
if (labelValue !== undefined) {
|
|
120
|
+
config.label = labelValue
|
|
121
|
+
}
|
|
122
|
+
if (Object.prototype.hasOwnProperty.call(value, 'joint')) {
|
|
123
|
+
config.joint = String(value.joint)
|
|
124
|
+
}
|
|
125
|
+
if (Object.prototype.hasOwnProperty.call(value, 'space')) {
|
|
126
|
+
config.space = String(value.space)
|
|
127
|
+
}
|
|
128
|
+
if (Object.keys(config).length === 0) return null
|
|
129
|
+
return config
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const mergeLabelConfig = (base, override) => {
|
|
133
|
+
if (!override) return base
|
|
134
|
+
const merged = { ...base }
|
|
135
|
+
if (Object.prototype.hasOwnProperty.call(override, 'label')) {
|
|
136
|
+
merged.label = override.label
|
|
137
|
+
}
|
|
138
|
+
if (Object.prototype.hasOwnProperty.call(override, 'joint')) {
|
|
139
|
+
merged.joint = override.joint
|
|
140
|
+
}
|
|
141
|
+
if (Object.prototype.hasOwnProperty.call(override, 'space')) {
|
|
142
|
+
merged.space = override.space
|
|
143
|
+
}
|
|
144
|
+
return merged
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const getDefaultLabelConfig = (labelLang) => {
|
|
148
|
+
const base = DEFAULT_LABEL_CONFIG_MAP[labelLang] || DEFAULT_LABEL_CONFIG_MAP.en
|
|
149
|
+
return { label: base.label, joint: base.joint, space: base.space }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const resolveLabelConfig = (opt) => {
|
|
153
|
+
let config = getDefaultLabelConfig(opt.labelLang)
|
|
154
|
+
let mapConfig = null
|
|
155
|
+
let singleConfig = null
|
|
156
|
+
if (opt.labelSet && typeof opt.labelSet === 'object') {
|
|
157
|
+
singleConfig = normalizeLabelConfig(opt.labelSet)
|
|
158
|
+
if (!singleConfig) {
|
|
159
|
+
mapConfig = normalizeLabelConfig(opt.labelSet[opt.labelLang])
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
config = mergeLabelConfig(config, mapConfig)
|
|
163
|
+
config = mergeLabelConfig(config, singleConfig)
|
|
164
|
+
|
|
165
|
+
if (!config.label) {
|
|
166
|
+
config.label = DEFAULT_LABEL_CONFIG_MAP.en.label
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (config.joint === undefined || config.space === undefined) {
|
|
170
|
+
const interWordSpace = getInterWordSpace(opt.labelLang, config.label)
|
|
171
|
+
if (config.joint === undefined) {
|
|
172
|
+
config.joint = interWordSpace ? '.' : ' '
|
|
173
|
+
}
|
|
174
|
+
if (config.space === undefined) {
|
|
175
|
+
config.space = interWordSpace ? ' ' : ' '
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return config
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const buildLabelPrefix = (labelConfig, hasCaption) => {
|
|
182
|
+
const labelText = labelConfig.label || ''
|
|
183
|
+
const joint = labelConfig.joint || ''
|
|
184
|
+
const space = labelConfig.space || ''
|
|
185
|
+
let prefix = labelText
|
|
186
|
+
|
|
187
|
+
if (joint) {
|
|
188
|
+
const jointIsWhitespace = whitespaceOnlyReg.test(joint)
|
|
189
|
+
if (hasCaption || !jointIsWhitespace) {
|
|
190
|
+
if (!prefix.endsWith(joint)) {
|
|
191
|
+
prefix += joint
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (hasCaption && space) {
|
|
197
|
+
if (!prefix.endsWith(space)) {
|
|
198
|
+
if (!(space === joint && prefix.endsWith(joint))) {
|
|
199
|
+
prefix += space
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return prefix
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const buildLabelMeta = (opt) => {
|
|
207
|
+
return resolveLabelConfig(opt)
|
|
208
|
+
}
|
|
209
|
+
|
|
1
210
|
const setMarkdownImgAttrToPCaption = (markdown, option) => {
|
|
2
211
|
|
|
3
212
|
const opt = {
|
|
4
213
|
imgAltCaption : true,
|
|
5
214
|
imgTitleCaption: false,
|
|
6
|
-
labelLang: '
|
|
215
|
+
labelLang: 'en',
|
|
216
|
+
autoLangDetection: true,
|
|
217
|
+
labelSet: null, // { label: '図', joint: ':', space: ' ' } or { ja: { label: '図', joint: ' ', space: '' }, en: { label: 'Figure', joint: '.', space: ' ' } }
|
|
7
218
|
}
|
|
8
|
-
if (option
|
|
219
|
+
if (option && typeof option === 'object') {
|
|
9
220
|
if (option.imgTitleCaption) {
|
|
10
221
|
opt.imgTitleCaption = option.imgTitleCaption
|
|
11
222
|
}
|
|
12
223
|
if (option.labelLang) {
|
|
13
224
|
opt.labelLang = option.labelLang
|
|
14
225
|
}
|
|
226
|
+
if (option.labelSet && typeof option.labelSet === 'object') {
|
|
227
|
+
opt.labelSet = option.labelSet
|
|
228
|
+
}
|
|
229
|
+
if (Object.prototype.hasOwnProperty.call(option, 'autoLangDetection')) {
|
|
230
|
+
opt.autoLangDetection = Boolean(option.autoLangDetection)
|
|
231
|
+
}
|
|
15
232
|
}
|
|
16
233
|
if (opt.imgTitleCaption) opt.imgAltCaption = false
|
|
17
234
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let lineBreaks = markdown.match(/\r\n|\n/g);
|
|
235
|
+
const lines = markdown.split(/\r\n|\n/)
|
|
236
|
+
const lineBreaks = markdown.match(/\r\n|\n/g) || []
|
|
21
237
|
let isBackquoteCodeBlock = false
|
|
22
238
|
let isTildeCodeBlock = false
|
|
23
|
-
const br = lineBreaks
|
|
239
|
+
const br = lineBreaks[0] || '\n'
|
|
240
|
+
|
|
241
|
+
let labelMeta = null
|
|
242
|
+
let autoLangChecked = !opt.autoLangDetection
|
|
24
243
|
|
|
25
244
|
if(lines.length === 0) return markdown
|
|
26
245
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (isBackquoteCodeBlock) {
|
|
32
|
-
isBackquoteCodeBlock = false
|
|
33
|
-
} else {
|
|
34
|
-
isBackquoteCodeBlock = true
|
|
35
|
-
}
|
|
246
|
+
for (let n = 0; n < lines.length; n++) {
|
|
247
|
+
const line = lines[n]
|
|
248
|
+
if (backquoteFenceReg.test(line)) {
|
|
249
|
+
isBackquoteCodeBlock = !isBackquoteCodeBlock
|
|
36
250
|
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
isTildeCodeBlock = false
|
|
40
|
-
} else {
|
|
41
|
-
isTildeCodeBlock = true
|
|
42
|
-
}
|
|
251
|
+
if (tildeFenceReg.test(line)) {
|
|
252
|
+
isTildeCodeBlock = !isTildeCodeBlock
|
|
43
253
|
}
|
|
44
254
|
if (isBackquoteCodeBlock || isTildeCodeBlock) {
|
|
45
|
-
n++
|
|
46
255
|
continue
|
|
47
256
|
}
|
|
48
257
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} else {
|
|
52
|
-
isPrevBreakLine = /^[ \t]*$/.test(lines[n-1])
|
|
53
|
-
}
|
|
54
|
-
if (n === lines.length -1) {
|
|
55
|
-
isNextBreakLine = true
|
|
56
|
-
} else {
|
|
57
|
-
isNextBreakLine = /^[ \t]*$/.test(lines[n+1])
|
|
58
|
-
}
|
|
258
|
+
const isPrevBreakLine = (n === 0) ? true : blankLineReg.test(lines[n-1])
|
|
259
|
+
const isNextBreakLine = (n === lines.length -1) ? true : blankLineReg.test(lines[n+1])
|
|
59
260
|
if (isPrevBreakLine && isNextBreakLine) {
|
|
60
|
-
|
|
261
|
+
if (line.indexOf(' !== -1) {
|
|
262
|
+
if (!autoLangChecked) {
|
|
263
|
+
const imgLine = line.match(imageLineReg)
|
|
264
|
+
if (imgLine) {
|
|
265
|
+
const rawText = getCaptionTextForDetection(imgLine, opt).trim()
|
|
266
|
+
if (rawText) {
|
|
267
|
+
const detected = detectAutoLang(rawText)
|
|
268
|
+
if (detected) {
|
|
269
|
+
opt.labelLang = detected
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
autoLangChecked = true
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!labelMeta && (!opt.autoLangDetection || autoLangChecked)) {
|
|
276
|
+
labelMeta = buildLabelMeta(opt)
|
|
277
|
+
}
|
|
278
|
+
if (labelMeta) {
|
|
279
|
+
lines[n] = modLines(n ,lines, br, opt, labelMeta)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
61
282
|
}
|
|
62
|
-
n++
|
|
63
283
|
}
|
|
64
284
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (n
|
|
69
|
-
|
|
70
|
-
break
|
|
285
|
+
const output = []
|
|
286
|
+
for (let n = 0; n < lines.length; n++) {
|
|
287
|
+
output.push(lines[n])
|
|
288
|
+
if (n < lines.length - 1) {
|
|
289
|
+
output.push(lineBreaks[n] || br)
|
|
71
290
|
}
|
|
72
|
-
markdown += lines[n] + lineBreaks[n]
|
|
73
|
-
n++
|
|
74
291
|
}
|
|
75
|
-
return
|
|
292
|
+
return output.join('')
|
|
76
293
|
}
|
|
77
294
|
|
|
78
|
-
const modLines = (n, lines, br, opt) => {
|
|
79
|
-
|
|
80
|
-
const markAfterNum = '[A-Z0-9]{1,6}(?:[.-][A-Z0-9]{1,6}){0,5}';
|
|
81
|
-
const joint = '[.:.。: ]';
|
|
82
|
-
const jointFullWidth = '[.。: ]';
|
|
83
|
-
const jointHalfWidth = '[.:]';
|
|
84
|
-
|
|
85
|
-
const markAfterEn = '(?:' +
|
|
86
|
-
' *(?:' +
|
|
87
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
88
|
-
jointFullWidth + '|' +
|
|
89
|
-
'(?=[ ]+[^0-9a-zA-Z])' +
|
|
90
|
-
')|' +
|
|
91
|
-
' *' + '(' + markAfterNum + ')(?:' +
|
|
92
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
93
|
-
jointFullWidth + '|' +
|
|
94
|
-
'(?=[ ]+[^a-z])|$' +
|
|
95
|
-
')|' +
|
|
96
|
-
'[.](' + markAfterNum + ')(?:' +
|
|
97
|
-
joint + '|(?=[ ]+[^a-z])|$' +
|
|
98
|
-
')' +
|
|
99
|
-
')';
|
|
100
|
-
const markAfterJa = '(?:' +
|
|
101
|
-
' *(?:' +
|
|
102
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
103
|
-
jointFullWidth + '|' +
|
|
104
|
-
'(?=[ ]+)' +
|
|
105
|
-
')|' +
|
|
106
|
-
' *' + '(' + markAfterNum + ')(?:' +
|
|
107
|
-
jointHalfWidth + '(?:(?=[ ]+)|$)|' +
|
|
108
|
-
jointFullWidth + '|' +
|
|
109
|
-
'(?=[ ]+)|$' +
|
|
110
|
-
')' +
|
|
111
|
-
')';
|
|
112
|
-
|
|
113
|
-
const labelEn = '(?:[fF][iI][gG](?:[uU][rR][eE])?|[iI][lL]{2}[uU][sS][tT]|[pP][hH][oO][tT][oO])';
|
|
114
|
-
const labelJa = '(?:図|イラスト|写真)';
|
|
115
|
-
|
|
116
|
-
const markReg = {
|
|
117
|
-
//fig(ure)?, illust, photo
|
|
118
|
-
"img": new RegExp('^(?:' + labelEn + markAfterEn + '|' + labelJa + markAfterJa +
|
|
119
|
-
')'),
|
|
120
|
-
}
|
|
121
|
-
const markRegWithNoJoint = {
|
|
122
|
-
"img": new RegExp('^(' + labelEn + '|' + labelJa + ')([ .]?' + markAfterNum + ')?$'),
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
let reg = /^([ \t]*?)!\[ *?(.*?) *?\]\(([^ ]*?)( +"(.*?)")?\)( *(?:{.*?})?)$/
|
|
126
|
-
|
|
127
|
-
const imgLine = lines[n].match(reg)
|
|
295
|
+
const modLines = (n, lines, br, opt, labelMeta) => {
|
|
296
|
+
const imgLine = lines[n].match(imageLineReg)
|
|
128
297
|
if (!imgLine) return lines[n]
|
|
129
298
|
//console.log(imgLine)
|
|
130
299
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let hasLabelWithNoJoint
|
|
135
|
-
if (opt.imgAltCaption) hasLabelWithNoJoint = imgLine[2].match(new RegExp(markRegWithNoJoint.img))
|
|
136
|
-
if (opt.imgTitleCaption) hasLabelWithNoJoint = imgLine[5].match(new RegExp(markRegWithNoJoint.img))
|
|
300
|
+
const captionText = getCaptionText(imgLine, opt)
|
|
301
|
+
const hasLabel = captionText ? captionText.match(markReg.img) : null
|
|
302
|
+
const hasLabelWithNoJoint = captionText && labelOnlyReg ? captionText.match(labelOnlyReg) : null
|
|
137
303
|
|
|
138
304
|
lines[n] = imgLine[1]
|
|
139
305
|
if (hasLabel) {
|
|
@@ -154,36 +320,22 @@ const setMarkdownImgAttrToPCaption = (markdown, option) => {
|
|
|
154
320
|
} else if (opt.imgTitleCaption) {
|
|
155
321
|
lines[n] += imgLine[5]
|
|
156
322
|
}
|
|
157
|
-
lines[n] += br + br + imgLine[1] + '![' + hasLabelWithNoJoint[0].replace(
|
|
323
|
+
lines[n] += br + br + imgLine[1] + '![' + hasLabelWithNoJoint[0].replace(jointSuffixReg, '') + ']'
|
|
158
324
|
} else {
|
|
159
325
|
//console.log('No label::')
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} else if (opt.imgTitleCaption) {
|
|
168
|
-
if (imgLine[5]) {
|
|
169
|
-
lines[n] += '図 ' + imgLine[5] + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
170
|
-
} else {
|
|
171
|
-
lines[n] += '図' + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
172
|
-
}
|
|
326
|
+
const hasCaption = captionText !== ''
|
|
327
|
+
const labelPrefix = buildLabelPrefix(labelMeta, hasCaption)
|
|
328
|
+
if (opt.imgAltCaption) {
|
|
329
|
+
if (hasCaption) {
|
|
330
|
+
lines[n] += labelPrefix + captionText + br + br + imgLine[1] + '![]'
|
|
331
|
+
} else {
|
|
332
|
+
lines[n] += labelPrefix + br + br + imgLine[1] + '![]'
|
|
173
333
|
}
|
|
174
|
-
} else if (opt.
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
lines[n] += 'Figure.' + br + br + imgLine[1] +'![]'
|
|
180
|
-
}
|
|
181
|
-
} else if (opt.imgTitleCaption) {
|
|
182
|
-
if (imgLine[5]) {
|
|
183
|
-
lines[n] += 'Figure. ' + imgLine[5] + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
184
|
-
} else {
|
|
185
|
-
lines[n] += 'Figure. ' + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
186
|
-
}
|
|
334
|
+
} else if (opt.imgTitleCaption) {
|
|
335
|
+
if (hasCaption) {
|
|
336
|
+
lines[n] += labelPrefix + captionText + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
337
|
+
} else {
|
|
338
|
+
lines[n] += labelPrefix + br + br + imgLine[1] + '![' + imgLine[2] + ']'
|
|
187
339
|
}
|
|
188
340
|
}
|
|
189
341
|
}
|
|
@@ -191,4 +343,4 @@ const setMarkdownImgAttrToPCaption = (markdown, option) => {
|
|
|
191
343
|
return lines[n]
|
|
192
344
|
}
|
|
193
345
|
|
|
194
|
-
export default setMarkdownImgAttrToPCaption
|
|
346
|
+
export default setMarkdownImgAttrToPCaption
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peaceroad/markdown-imgattr-to-pcaption",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Change img alt attribute to figure caption paragraph for p7d-markdown-it-p-captions.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.js",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
7
12
|
"scripts": {
|
|
8
13
|
"test": "node test/test.js"
|
|
9
14
|
},
|
|
@@ -16,5 +21,8 @@
|
|
|
16
21
|
"bugs": {
|
|
17
22
|
"url": "https://github.com/peaceroad/markdown-imgattr-to-pcaption/issues"
|
|
18
23
|
},
|
|
19
|
-
"homepage": "https://github.com/peaceroad/markdown-imgattr-to-pcaption#readme"
|
|
24
|
+
"homepage": "https://github.com/peaceroad/markdown-imgattr-to-pcaption#readme",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"p7d-markdown-it-p-captions": "^0.20.0"
|
|
27
|
+
}
|
|
20
28
|
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
[Input]
|
|
2
|
-
段落。段落。段落。
|
|
3
|
-
|
|
4
|
-

|
|
5
|
-
|
|
6
|
-
段落。段落。段落。
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[Output]
|
|
10
|
-
段落。段落。段落。
|
|
11
|
-
|
|
12
|
-
図 キャプション
|
|
13
|
-
|
|
14
|
-

|
|
15
|
-
|
|
16
|
-
段落。段落。段落。
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
[Input]
|
|
20
|
-
段落。段落。段落。
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
段落。段落。段落。
|
|
25
|
-
|
|
26
|
-
[Output]
|
|
27
|
-
段落。段落。段落。
|
|
28
|
-
|
|
29
|
-
図 キャプション
|
|
30
|
-
|
|
31
|
-

|
|
32
|
-
|
|
33
|
-
段落。段落。段落。
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[Input]
|
|
38
|
-
段落。段落。段落。
|
|
39
|
-
|
|
40
|
-

|
|
41
|
-
|
|
42
|
-
段落。段落。段落。
|
|
43
|
-
|
|
44
|
-
[Output]
|
|
45
|
-
段落。段落。段落。
|
|
46
|
-
|
|
47
|
-
図1 キャプション
|
|
48
|
-
|
|
49
|
-

|
|
50
|
-
|
|
51
|
-
段落。段落。段落。
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[Input]
|
|
56
|
-
段落。段落。段落。
|
|
57
|
-
|
|
58
|
-

|
|
59
|
-
|
|
60
|
-
段落。段落。段落。
|
|
61
|
-
|
|
62
|
-
[Output]
|
|
63
|
-
段落。段落。段落。
|
|
64
|
-
|
|
65
|
-
図1 キャプション
|
|
66
|
-
|
|
67
|
-

|
|
68
|
-
|
|
69
|
-
段落。段落。段落。
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
[Input]
|
|
74
|
-
段落。段落。段落。
|
|
75
|
-
|
|
76
|
-

|
|
77
|
-
|
|
78
|
-
段落。段落。段落。
|
|
79
|
-
|
|
80
|
-
[Output]
|
|
81
|
-
段落。段落。段落。
|
|
82
|
-
|
|
83
|
-
図1:キャプション
|
|
84
|
-
|
|
85
|
-

|
|
86
|
-
|
|
87
|
-
段落。段落。段落。
|
package/test/examples.txt
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
[Input]
|
|
2
|
-
段落。段落。段落。
|
|
3
|
-
|
|
4
|
-

|
|
5
|
-
|
|
6
|
-
段落。段落。段落。
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[Output]
|
|
10
|
-
段落。段落。段落。
|
|
11
|
-
|
|
12
|
-
図 キャプション
|
|
13
|
-
|
|
14
|
-

|
|
15
|
-
|
|
16
|
-
段落。段落。段落。
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
[Input]
|
|
20
|
-
段落。段落。段落。
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
段落。段落。段落。
|
|
25
|
-
|
|
26
|
-
[Output]
|
|
27
|
-
段落。段落。段落。
|
|
28
|
-
|
|
29
|
-
図 キャプション
|
|
30
|
-
|
|
31
|
-

|
|
32
|
-
|
|
33
|
-
段落。段落。段落。
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[Input]
|
|
38
|
-
段落。段落。段落。
|
|
39
|
-
|
|
40
|
-

|
|
41
|
-
|
|
42
|
-
段落。段落。段落。
|
|
43
|
-
|
|
44
|
-
[Output]
|
|
45
|
-
段落。段落。段落。
|
|
46
|
-
|
|
47
|
-
図1 キャプション
|
|
48
|
-
|
|
49
|
-

|
|
50
|
-
|
|
51
|
-
段落。段落。段落。
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
[Input]
|
|
56
|
-
段落。段落。段落。
|
|
57
|
-
|
|
58
|
-

|
|
59
|
-
|
|
60
|
-
段落。段落。段落。
|
|
61
|
-
|
|
62
|
-
[Output]
|
|
63
|
-
段落。段落。段落。
|
|
64
|
-
|
|
65
|
-
図1 キャプション
|
|
66
|
-
|
|
67
|
-

|
|
68
|
-
|
|
69
|
-
段落。段落。段落。
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
[Input]
|
|
73
|
-
段落。段落。段落。
|
|
74
|
-
|
|
75
|
-

|
|
76
|
-
|
|
77
|
-
段落。段落。段落。
|
|
78
|
-
|
|
79
|
-
[Output]
|
|
80
|
-
段落。段落。段落。
|
|
81
|
-
|
|
82
|
-
図1:キャプション
|
|
83
|
-
|
|
84
|
-

|
|
85
|
-
|
|
86
|
-
段落。段落。段落。
|
|
87
|
-
|
|
88
|
-
|
package/test/test.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import assert from 'assert'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import setMarkdownImgAttrToPCaption from '../index.js'
|
|
5
|
-
|
|
6
|
-
let __dirname = path.dirname(new URL(import.meta.url).pathname)
|
|
7
|
-
const isWindows = (process.platform === 'win32')
|
|
8
|
-
if (isWindows) {
|
|
9
|
-
__dirname = __dirname.replace(/^\/+/, '').replace(/\//g, '\\')
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const check = (name, ex) => {
|
|
13
|
-
const exCont = fs.readFileSync(ex, 'utf-8').trim()
|
|
14
|
-
let ms = [];
|
|
15
|
-
let ms0 = exCont.split(/\n*\[Input\]\n/)
|
|
16
|
-
let n = 1;
|
|
17
|
-
while(n < ms0.length) {
|
|
18
|
-
let mhs = ms0[n].split(/\n+\[Output[^\]]*?\]\n/)
|
|
19
|
-
let i = 1
|
|
20
|
-
while (i < 2) {
|
|
21
|
-
if (mhs[i] === undefined) {
|
|
22
|
-
mhs[i] = ''
|
|
23
|
-
} else {
|
|
24
|
-
mhs[i] = mhs[i].replace(/$/,'\n')
|
|
25
|
-
}
|
|
26
|
-
i++
|
|
27
|
-
}
|
|
28
|
-
ms[n] = {
|
|
29
|
-
inputMarkdown: mhs[0].trim(),
|
|
30
|
-
outputMarkdown: mhs[1].trim(),
|
|
31
|
-
};
|
|
32
|
-
n++
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
n = 1
|
|
36
|
-
while(n < ms.length) {
|
|
37
|
-
//if (n !== 10) { n++; continue }
|
|
38
|
-
console.log('Test: ' + n + ' >>>')
|
|
39
|
-
const m = ms[n].inputMarkdown
|
|
40
|
-
let h
|
|
41
|
-
let option = {}
|
|
42
|
-
if (name === 'default') {
|
|
43
|
-
h = setMarkdownImgAttrToPCaption(m)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (name === 'imgTitleAttr') {
|
|
48
|
-
h = setMarkdownImgAttrToPCaption(m, {imgTitleCaption: true})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (name === 'labelLang') {
|
|
52
|
-
h = setMarkdownImgAttrToPCaption(m, {labelLang: 'en'})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
assert.strictEqual(h, ms[n].outputMarkdown)
|
|
58
|
-
} catch(e) {
|
|
59
|
-
console.log('incorrect: ')
|
|
60
|
-
//console.log(m)
|
|
61
|
-
//console.log('::convert ->')
|
|
62
|
-
console.log('H: ' + h +'\n\nC: ' + ms[n].outputMarkdown)
|
|
63
|
-
}
|
|
64
|
-
n++
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const example = {
|
|
70
|
-
default: __dirname + path.sep + 'examples.txt',
|
|
71
|
-
imgTitleAttr: __dirname + path.sep + 'examples-img-title-attr.txt',
|
|
72
|
-
labelLang: __dirname + path.sep + 'examples-label-lang.txt',
|
|
73
|
-
}
|
|
74
|
-
for (let ex in example) {
|
|
75
|
-
console.log('[Test] ' + ex)
|
|
76
|
-
check(ex, example[ex])
|
|
77
|
-
}
|