@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.2.0 → 0.2.1
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 +336 -335
- package/debug/dump_listinfos.mjs +65 -0
- package/debug/dump_tokens.mjs +72 -0
- package/index.js +5 -0
- package/package.json +5 -2
- package/src/phase0-description-list.js +654 -654
- package/src/phase1-analyze.js +90 -7
- package/src/phase2-convert.js +1037 -738
- package/src/phase3-attributes.js +260 -244
- package/src/phase5-spans.js +1 -1
- package/src/phase6-attrs-migration.js +1 -1
- package/src/preprocess-literal-lists.js +776 -0
- package/src/types-utility.js +995 -991
package/src/types-utility.js
CHANGED
|
@@ -1,991 +1,995 @@
|
|
|
1
|
-
// ListTypes.json related utilities and marker processing
|
|
2
|
-
|
|
3
|
-
import types from '../listTypes.json' with { type: 'json' }
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Check if a marker type is convertible in default mode
|
|
7
|
-
* Exotic markers that aren't commonly used are excluded from conversion
|
|
8
|
-
* @param {string} markerType - The marker type name (e.g., 'decimal', 'lower-greek')
|
|
9
|
-
* @returns {boolean} True if the marker type should be converted in default mode
|
|
10
|
-
*/
|
|
11
|
-
export const isConvertibleMarkerType = (markerType) => {
|
|
12
|
-
if (!markerType) return false
|
|
13
|
-
|
|
14
|
-
// Exclude exotic markers that should remain as <ul> in default mode
|
|
15
|
-
// These are rarely used and may not be well-supported
|
|
16
|
-
const excludedTypes = [
|
|
17
|
-
'fullwidth-lower-roman',
|
|
18
|
-
'fullwidth-upper-roman',
|
|
19
|
-
'squared-upper-latin',
|
|
20
|
-
'filled-squared-upper-latin'
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
return !excludedTypes.includes(markerType)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const escapeRegExp = (string) => {
|
|
27
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Normalize fullwidth digits to ASCII digits
|
|
31
|
-
const normalizeFullwidthDigits = (str) => {
|
|
32
|
-
if (!str || typeof str !== 'string') return str
|
|
33
|
-
return str.replace(/[0-9]/g, ch => {
|
|
34
|
-
const code = ch.codePointAt(0)
|
|
35
|
-
return String.fromCharCode(code - 0xFF10 + 48)
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Resolve top-level pattern groups once at module initialization.
|
|
40
|
-
// Use `patternGroups` top-level key from `listTypes.json`.
|
|
41
|
-
const PATTERN_GROUPS = Array.isArray(types.patternGroups) ? types.patternGroups : []
|
|
42
|
-
|
|
43
|
-
// Map for O(1) name -> group lookup
|
|
44
|
-
const PATTERN_GROUP_MAP = new Map((PATTERN_GROUPS || []).map(g => [g.name, g]))
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get pattern list by name.
|
|
48
|
-
* Accepts a single name (string), an array of names, or a pattern-group-like object.
|
|
49
|
-
*/
|
|
50
|
-
const getPatternsByName = (patternName) => {
|
|
51
|
-
if (!patternName) return []
|
|
52
|
-
// If caller passed the actual group object
|
|
53
|
-
if (typeof patternName === 'object' && Array.isArray(patternName.patterns)) {
|
|
54
|
-
return patternName.patterns
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// If caller passed an array of group names, merge their patterns in order
|
|
58
|
-
if (Array.isArray(patternName)) {
|
|
59
|
-
const out = []
|
|
60
|
-
for (const name of patternName) {
|
|
61
|
-
if (typeof name !== 'string') continue
|
|
62
|
-
const g = PATTERN_GROUP_MAP.get(name)
|
|
63
|
-
if (g && Array.isArray(g.patterns)) out.push(...g.patterns)
|
|
64
|
-
}
|
|
65
|
-
return out
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Single name lookup (O(1) via map)
|
|
69
|
-
if (typeof patternName === 'string') {
|
|
70
|
-
const patternGroup = PATTERN_GROUP_MAP.get(patternName)
|
|
71
|
-
return Array.isArray(patternGroup?.patterns) ? patternGroup.patterns : []
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return []
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Precompiled common regexes to avoid recreating them repeatedly
|
|
78
|
-
const ROMAN_LIKE_REGEX = /^[IVX]+\.?$/i
|
|
79
|
-
const LATIN_LETTER_REGEX = /^[a-z]$/i
|
|
80
|
-
const MARKER_FALLBACK_REGEX = /^(\S+)/
|
|
81
|
-
const PURE_PREFIX_REMOVAL_REGEX = /^[\((]+/
|
|
82
|
-
|
|
83
|
-
// Build dynamic suffix character classes from `listTypes.json` patterns so
|
|
84
|
-
// edits to that file don't require touching this code.
|
|
85
|
-
const _buildSuffixCharSets = () => {
|
|
86
|
-
const all = new Set()
|
|
87
|
-
const fullwidth = new Set()
|
|
88
|
-
const groups = PATTERN_GROUPS
|
|
89
|
-
if (groups.length > 0) {
|
|
90
|
-
for (const group of groups) {
|
|
91
|
-
if (!group || !Array.isArray(group.patterns)) continue
|
|
92
|
-
for (const p of group.patterns) {
|
|
93
|
-
if (!p || !p.suffix) continue
|
|
94
|
-
for (const ch of Array.from(p.suffix)) {
|
|
95
|
-
all.add(ch)
|
|
96
|
-
const cp = ch.codePointAt(0)
|
|
97
|
-
if (cp !== undefined && cp > 0xFF) fullwidth.add(ch)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return { all, fullwidth }
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const { all: _ALL_SUFFIX_CHARS, fullwidth: _FULLWIDTH_SUFFIX_CHARS } = _buildSuffixCharSets()
|
|
106
|
-
|
|
107
|
-
// Reuse existing `escapeRegExp` for char-class escaping to avoid duplication
|
|
108
|
-
const SUFFIX_CHAR_CLASS = [..._ALL_SUFFIX_CHARS].map(ch => escapeRegExp(ch)).join('')
|
|
109
|
-
|
|
110
|
-
const SUFFIX_CLASS_FOR_REGEX = SUFFIX_CHAR_CLASS.length > 0 ? `[${SUFFIX_CHAR_CLASS}]` : "\\."
|
|
111
|
-
|
|
112
|
-
const MARKER_SUFFIX_SPACE_REGEX = new RegExp(`^([^\\s]+?${SUFFIX_CLASS_FOR_REGEX})\\s`)
|
|
113
|
-
const MARKER_SUFFIX_NO_SPACE_REGEX = new RegExp(`^([^\\s]+?${SUFFIX_CLASS_FOR_REGEX})(?=[^\\s])`)
|
|
114
|
-
const PURE_SUFFIXES_REMOVAL_REGEX = new RegExp(`${SUFFIX_CLASS_FOR_REGEX}+$`)
|
|
115
|
-
|
|
116
|
-
// Cached type separation
|
|
117
|
-
let _symbolBasedTypes = null
|
|
118
|
-
let _rangeBasedTypes = null
|
|
119
|
-
let _sortedSymbolTypes = null
|
|
120
|
-
let _typeInfoByName = null
|
|
121
|
-
|
|
122
|
-
const getTypeSeparation = () => {
|
|
123
|
-
if (_symbolBasedTypes === null) {
|
|
124
|
-
const allTypes = compiledTypes()
|
|
125
|
-
_symbolBasedTypes = []
|
|
126
|
-
_rangeBasedTypes = []
|
|
127
|
-
_typeInfoByName = new Map()
|
|
128
|
-
|
|
129
|
-
// Create typeInfo lookup map
|
|
130
|
-
for (const type of types.types) {
|
|
131
|
-
_typeInfoByName.set(type.name, type)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
for (const compiledType of allTypes) {
|
|
135
|
-
const typeInfo = _typeInfoByName.get(compiledType.name)
|
|
136
|
-
if (typeInfo?.symbols) {
|
|
137
|
-
_symbolBasedTypes.push(compiledType)
|
|
138
|
-
} else {
|
|
139
|
-
_rangeBasedTypes.push(compiledType)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Sort symbol-based types to prioritize Roman numerals over Latin letters
|
|
144
|
-
_sortedSymbolTypes = [..._symbolBasedTypes].sort((a, b) => {
|
|
145
|
-
if (a.name.includes('roman') && b.name.includes('latin')) return -1
|
|
146
|
-
if (a.name.includes('latin') && b.name.includes('roman')) return 1
|
|
147
|
-
return 0
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
symbolBasedTypes: _symbolBasedTypes,
|
|
152
|
-
rangeBasedTypes: _rangeBasedTypes,
|
|
153
|
-
sortedSymbolTypes: _sortedSymbolTypes,
|
|
154
|
-
typeInfoByName: _typeInfoByName
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Get start value from type info
|
|
160
|
-
* @param {Object} typeInfo - Type information
|
|
161
|
-
* @returns {number} Start value (default 1)
|
|
162
|
-
*/
|
|
163
|
-
const getStartValue = (typeInfo) => typeInfo?.start !== undefined ? typeInfo.start : 1
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Check if symbols match a specific sequence pattern
|
|
167
|
-
* @param {Array<string>} pureSymbols - Array of pure symbols (without prefix/suffix)
|
|
168
|
-
* @param {Array<string>} sequence - Expected sequence array
|
|
169
|
-
* @param {string} typeName - Name of the type to return
|
|
170
|
-
* @param {boolean} allSame - Whether all symbols are the same
|
|
171
|
-
* @returns {Object|null} Type object or null
|
|
172
|
-
*/
|
|
173
|
-
const checkSequenceMatch = (pureSymbols, sequence, typeName, allSame) => {
|
|
174
|
-
if (allSame && pureSymbols.length >= 1) {
|
|
175
|
-
if (sequence.indexOf(pureSymbols[0]) !== -1) {
|
|
176
|
-
return { type: typeName }
|
|
177
|
-
}
|
|
178
|
-
} else if (pureSymbols.length >= 2) {
|
|
179
|
-
// Find where the first symbol appears in the sequence
|
|
180
|
-
const startIndex = sequence.indexOf(pureSymbols[0])
|
|
181
|
-
if (startIndex === -1) return null
|
|
182
|
-
|
|
183
|
-
// Check if all symbols match a consecutive part of the sequence
|
|
184
|
-
let isValidSequence = true
|
|
185
|
-
for (let i = 0; i < pureSymbols.length; i++) {
|
|
186
|
-
const expectedIndex = startIndex + i
|
|
187
|
-
if (expectedIndex >= sequence.length || pureSymbols[i] !== sequence[expectedIndex]) {
|
|
188
|
-
isValidSequence = false
|
|
189
|
-
break
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (isValidSequence) {
|
|
193
|
-
return { type: typeName }
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return null
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Check if array matches expected sequence from start
|
|
201
|
-
* @param {Array<string>} actual - Actual symbols
|
|
202
|
-
* @param {Array<string>} expected - Expected sequence
|
|
203
|
-
* @returns {boolean} True if matches
|
|
204
|
-
*/
|
|
205
|
-
const matchesSequence = (actual, expected) => {
|
|
206
|
-
for (let i = 0; i < actual.length; i++) {
|
|
207
|
-
if (actual[i] !== expected[i]) {
|
|
208
|
-
return false
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return true
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Extract pure symbol by removing prefix and suffix
|
|
216
|
-
* @param {string} marker - The marker with prefix/suffix
|
|
217
|
-
* @param {string|null} prefix - The prefix to remove
|
|
218
|
-
* @param {string|null} suffix - The suffix to remove
|
|
219
|
-
* @returns {string} Pure symbol without prefix/suffix
|
|
220
|
-
*/
|
|
221
|
-
const extractPureSymbol = (marker, prefix, suffix) => {
|
|
222
|
-
let start = prefix ? prefix.length : 0
|
|
223
|
-
let end = suffix ? marker.length - suffix.length : marker.length
|
|
224
|
-
return marker.substring(start, end)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Calculate number for a marker based on type info
|
|
229
|
-
* @param {Object} typeInfo - Type information from listTypes.json
|
|
230
|
-
* @param {string} pureSymbol - Pure symbol without prefix/suffix
|
|
231
|
-
* @returns {number|undefined} The calculated number
|
|
232
|
-
*/
|
|
233
|
-
const calculateNumber = (typeInfo, pureSymbol, compiled = null) => {
|
|
234
|
-
if (!typeInfo || !pureSymbol) return undefined
|
|
235
|
-
|
|
236
|
-
if (typeInfo.symbols) {
|
|
237
|
-
// Symbol-based types (katakana, roman numerals, etc.)
|
|
238
|
-
// Prefer a provided compiled object (with symbolIndexMap) to avoid map lookups.
|
|
239
|
-
if (compiled && compiled.symbolIndexMap) {
|
|
240
|
-
const idx = compiled.symbolIndexMap.get(pureSymbol)
|
|
241
|
-
return idx !== undefined ? idx + getStartValue(typeInfo) : undefined
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Fall back to looking up compiled map once (lazy) if compiled not provided
|
|
245
|
-
const compiledFallback = _COMPILED_BY_NAME.get(typeInfo.name)
|
|
246
|
-
if (compiledFallback && compiledFallback.symbolIndexMap) {
|
|
247
|
-
const idx = compiledFallback.symbolIndexMap.get(pureSymbol)
|
|
248
|
-
return idx !== undefined ? idx + getStartValue(typeInfo) : undefined
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const symbolIndex = typeInfo.symbols.indexOf(pureSymbol)
|
|
252
|
-
return symbolIndex !== -1 ? symbolIndex + getStartValue(typeInfo) : undefined
|
|
253
|
-
} else if (typeInfo.range) {
|
|
254
|
-
// Range-based types (lower-latin, upper-latin, decimal)
|
|
255
|
-
const startValue = getStartValue(typeInfo)
|
|
256
|
-
if (Array.isArray(typeInfo.range) && typeof typeInfo.range[0] === 'string') {
|
|
257
|
-
// Latin range: ["a", "z"] or ["A", "Z"]
|
|
258
|
-
const startChar = typeInfo.range[0]
|
|
259
|
-
if (pureSymbol.length > 0) {
|
|
260
|
-
// Use codePointAt for proper Unicode handling (including surrogate pairs)
|
|
261
|
-
const charCode = pureSymbol.codePointAt(0)
|
|
262
|
-
const startCharCode = startChar.codePointAt(0)
|
|
263
|
-
return charCode - startCharCode + startValue
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
// Numeric range: [start, end]
|
|
267
|
-
const norm = normalizeFullwidthDigits(pureSymbol)
|
|
268
|
-
const numValue = parseInt(norm, 10)
|
|
269
|
-
if (!isNaN(numValue)) {
|
|
270
|
-
return numValue
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return undefined
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Detect sequence pattern from multiple contents
|
|
279
|
-
export const detectSequencePattern = (allContents) => {
|
|
280
|
-
if (!allContents || allContents.length < 1) return null
|
|
281
|
-
|
|
282
|
-
// Extract pure markers from all contents
|
|
283
|
-
const markers = allContents.map(content => {
|
|
284
|
-
const trimmed = content.trim()
|
|
285
|
-
// First try: symbol(s) followed by suffix and optional space
|
|
286
|
-
let match = trimmed.match(MARKER_SUFFIX_SPACE_REGEX)
|
|
287
|
-
if (match) return match[1]
|
|
288
|
-
|
|
289
|
-
// Second try: symbol(s) followed by suffix at end (no space after)
|
|
290
|
-
match = trimmed.match(MARKER_SUFFIX_NO_SPACE_REGEX)
|
|
291
|
-
if (match) return match[1]
|
|
292
|
-
|
|
293
|
-
// Fallback: everything before first space
|
|
294
|
-
match = trimmed.match(MARKER_FALLBACK_REGEX)
|
|
295
|
-
return match ? match[1] : trimmed
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
// Extract pure symbols (remove prefixes/suffixes)
|
|
299
|
-
const pureSymbols = markers.map(marker => {
|
|
300
|
-
return marker.replace(PURE_SUFFIXES_REMOVAL_REGEX, '').replace(PURE_PREFIX_REMOVAL_REGEX, '')
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
// Check if all symbols are the same (repeated marker case)
|
|
304
|
-
const allSame = pureSymbols.every(s => s === pureSymbols[0])
|
|
305
|
-
|
|
306
|
-
// Cache type lookups
|
|
307
|
-
const { typeInfoByName } = getTypeSeparation()
|
|
308
|
-
const irohaType = typeInfoByName.get('katakana-iroha')
|
|
309
|
-
const katakanaType = typeInfoByName.get('katakana')
|
|
310
|
-
const upperRomanType = typeInfoByName.get('upper-roman')
|
|
311
|
-
|
|
312
|
-
// Get symbol sequences from listTypes.json
|
|
313
|
-
if (irohaType?.symbols) {
|
|
314
|
-
const irohaResult = checkSequenceMatch(pureSymbols, irohaType.symbols, 'katakana-iroha', allSame)
|
|
315
|
-
if (irohaResult) return irohaResult
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (katakanaType?.symbols) {
|
|
319
|
-
const katakanaResult = checkSequenceMatch(pureSymbols, katakanaType.symbols, 'katakana', allSame)
|
|
320
|
-
if (katakanaResult) return katakanaResult
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Check Roman numerals from listTypes.json
|
|
324
|
-
if (upperRomanType?.symbols) {
|
|
325
|
-
const allRomanLike = markers.every(marker => ROMAN_LIKE_REGEX.test(marker))
|
|
326
|
-
|
|
327
|
-
if (allRomanLike) {
|
|
328
|
-
const romanSymbols = markers.map(marker => marker.replace(/\.$/, '').toUpperCase())
|
|
329
|
-
if (matchesSequence(romanSymbols, upperRomanType.symbols)) {
|
|
330
|
-
return { type: 'upper-roman' }
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Check Latin letters (range-based types)
|
|
336
|
-
const allLatinLike = pureSymbols.every(symbol => LATIN_LETTER_REGEX.test(symbol))
|
|
337
|
-
|
|
338
|
-
if (allLatinLike && pureSymbols.length >= 2) {
|
|
339
|
-
const firstSymbol = pureSymbols[0]
|
|
340
|
-
const isLowerCase = firstSymbol === firstSymbol.toLowerCase()
|
|
341
|
-
const baseCharCode = isLowerCase ? firstSymbol.charCodeAt(0) : firstSymbol.toUpperCase().charCodeAt(0)
|
|
342
|
-
|
|
343
|
-
const expectedSequence = pureSymbols.map((_, i) => {
|
|
344
|
-
const char = String.fromCharCode(baseCharCode + i)
|
|
345
|
-
return isLowerCase ? char : char.toUpperCase()
|
|
346
|
-
})
|
|
347
|
-
const actualSequence = pureSymbols.map(s => isLowerCase ? s : s.toUpperCase())
|
|
348
|
-
|
|
349
|
-
if (matchesSequence(actualSequence, expectedSequence)) {
|
|
350
|
-
return { type: isLowerCase ? 'lower-latin' : 'upper-latin' }
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return null
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Create marker detection result object
|
|
359
|
-
* @param {string} type - Marker type name
|
|
360
|
-
* @param {string} marker - Detected marker
|
|
361
|
-
* @param {number|undefined} number - Calculated number
|
|
362
|
-
* @param {string|null} prefix - Prefix character
|
|
363
|
-
* @param {string|null} suffix - Suffix character
|
|
364
|
-
* @returns {Object} Detection result
|
|
365
|
-
*/
|
|
366
|
-
const createMarkerResult = (type, marker, number, prefix, suffix) => ({
|
|
367
|
-
type,
|
|
368
|
-
marker,
|
|
369
|
-
number,
|
|
370
|
-
prefix,
|
|
371
|
-
suffix
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Try to match content against compiled type patterns
|
|
376
|
-
* @param {string} trimmed - Trimmed content
|
|
377
|
-
* @param {Object} compiledType - Compiled type info
|
|
378
|
-
* @param {Object} typeInfo - Type info from listTypes.json
|
|
379
|
-
* @returns {Object|null} Match result or null
|
|
380
|
-
*/
|
|
381
|
-
const tryMatchPattern = (trimmed, compiledType, typeInfo) => {
|
|
382
|
-
for (const pattern of compiledType.patterns) {
|
|
383
|
-
const m = matchRegexEntry(trimmed, compiledType.name, pattern)
|
|
384
|
-
if (m) return m
|
|
385
|
-
}
|
|
386
|
-
return null
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Enhanced marker type detection with context awareness
|
|
390
|
-
export const detectMarkerType = (content, allContents = null) => {
|
|
391
|
-
if (!content || typeof content !== 'string') {
|
|
392
|
-
return { type: null, marker: null }
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const trimmed = content.trim()
|
|
396
|
-
if (!trimmed) return { type: null, marker: null }
|
|
397
|
-
|
|
398
|
-
const { sortedSymbolTypes, rangeBasedTypes, typeInfoByName } = getTypeSeparation()
|
|
399
|
-
|
|
400
|
-
// If we have context (even single element), try to detect the overall pattern first
|
|
401
|
-
if (allContents && Array.isArray(allContents) && allContents.length >= 1) {
|
|
402
|
-
const contextResult = detectSequencePattern(allContents)
|
|
403
|
-
if (contextResult) {
|
|
404
|
-
// If context suggests a specific type, verify current content matches
|
|
405
|
-
const typeInfo = typeInfoByName.get(contextResult.type)
|
|
406
|
-
if (typeInfo) {
|
|
407
|
-
// Check both symbol-based and range-based types
|
|
408
|
-
const allCompiledTypes = [...sortedSymbolTypes, ...rangeBasedTypes]
|
|
409
|
-
for (const compiledType of allCompiledTypes) {
|
|
410
|
-
if (compiledType.name === contextResult.type) {
|
|
411
|
-
const matchResult = tryMatchPattern(trimmed, compiledType, typeInfo)
|
|
412
|
-
if (matchResult) return matchResult
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Fallback to original logic
|
|
420
|
-
// Fast fallback: try a flattened precompiled pattern list to avoid nested loops
|
|
421
|
-
const flatMatch = tryMatchAgainstFlattened(trimmed)
|
|
422
|
-
if (flatMatch) return flatMatch
|
|
423
|
-
|
|
424
|
-
return { type: null, marker: null }
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Get the symbol for a specific number in a marker type
|
|
429
|
-
* @param {string} markerType - The marker type name (e.g., 'katakana-iroha', 'lower-roman')
|
|
430
|
-
* @param {number} number - The 1-based number (e.g., 1 for first item, 2 for second)
|
|
431
|
-
* @returns {string|null} The symbol for that number, or null if not found
|
|
432
|
-
*/
|
|
433
|
-
export const getSymbolForNumber = (markerType, number) => {
|
|
434
|
-
const { typeInfoByName } = getTypeSeparation()
|
|
435
|
-
const typeInfo = typeInfoByName.get(markerType)
|
|
436
|
-
if (!typeInfo) {
|
|
437
|
-
return null
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// For symbol-based types
|
|
441
|
-
if (typeInfo.symbols && Array.isArray(typeInfo.symbols)) {
|
|
442
|
-
const startValue = getStartValue(typeInfo)
|
|
443
|
-
const index = number - startValue
|
|
444
|
-
if (index >= 0 && index < typeInfo.symbols.length) {
|
|
445
|
-
return typeInfo.symbols[index]
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// For range-based types (latin, decimal)
|
|
450
|
-
if (typeInfo.range) {
|
|
451
|
-
const startValue = getStartValue(typeInfo)
|
|
452
|
-
if (Array.isArray(typeInfo.range) && typeInfo.range.length === 2) {
|
|
453
|
-
// Latin range: ["a", "z"] or ["A", "Z"]
|
|
454
|
-
const startChar = typeInfo.range[0]
|
|
455
|
-
// Ensure startChar is a string
|
|
456
|
-
if (typeof startChar === 'string' && startChar.length > 0) {
|
|
457
|
-
// Use codePointAt and fromCodePoint for proper Unicode handling
|
|
458
|
-
const startCharCode = startChar.codePointAt(0)
|
|
459
|
-
const offset = number - startValue
|
|
460
|
-
const targetCharCode = startCharCode + offset
|
|
461
|
-
return String.fromCodePoint(targetCharCode)
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// Numeric range or fallback
|
|
465
|
-
return String(number)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return null
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Get the default prefix/suffix pattern for a marker type
|
|
473
|
-
* @param {string} markerType - The marker type name (e.g., 'lower-roman', 'decimal')
|
|
474
|
-
* @returns {Object} Object with prefix and suffix properties
|
|
475
|
-
*/
|
|
476
|
-
export const getDefaultPatternForType = (markerType) => {
|
|
477
|
-
const { typeInfoByName } = getTypeSeparation()
|
|
478
|
-
const typeInfo = typeInfoByName.get(markerType)
|
|
479
|
-
if (!typeInfo) {
|
|
480
|
-
return { prefix: '', suffix: '.' }
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Get patterns for this type (prefer `pattern` property)
|
|
484
|
-
const patternRef = typeInfo.pattern || null
|
|
485
|
-
const patterns = getPatternsByName(patternRef)
|
|
486
|
-
if (!patterns || patterns.length === 0) {
|
|
487
|
-
return { prefix: '', suffix: '.' }
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Return the first pattern as the default
|
|
491
|
-
return {
|
|
492
|
-
prefix: patterns[0].prefix || '',
|
|
493
|
-
suffix: patterns[0].suffix || '.'
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const prefixs = [
|
|
498
|
-
['(', 'round'],
|
|
499
|
-
//['[', 'square'],
|
|
500
|
-
//['{', 'curly'],
|
|
501
|
-
//['<', 'angle'],
|
|
502
|
-
['(', 'fullround'],
|
|
503
|
-
]
|
|
504
|
-
|
|
505
|
-
const suffixs = [
|
|
506
|
-
[')', 'round'],
|
|
507
|
-
//[']', 'square'],
|
|
508
|
-
//['}', 'curly'],
|
|
509
|
-
//['>', 'angle'],
|
|
510
|
-
[')', 'fullround'],
|
|
511
|
-
]
|
|
512
|
-
|
|
513
|
-
// Build Maps for O(1) lookups (faster than .find on every call)
|
|
514
|
-
const prefixMap = new Map(prefixs)
|
|
515
|
-
const suffixMap = new Map(suffixs)
|
|
516
|
-
|
|
517
|
-
const generateClassName = (baseClass, prefix, suffix) => {
|
|
518
|
-
// fast path: no prefix and no suffix
|
|
519
|
-
if (!prefix && !suffix) return baseClass
|
|
520
|
-
|
|
521
|
-
// O(1) map lookups
|
|
522
|
-
const prefixName = prefixMap.get(prefix) || null
|
|
523
|
-
const suffixName = suffixMap.get(suffix) || null
|
|
524
|
-
|
|
525
|
-
// If neither side matches known labels, return baseClass
|
|
526
|
-
if (!prefixName && !suffixName) return baseClass
|
|
527
|
-
|
|
528
|
-
const p = prefixName ? prefixName : 'none'
|
|
529
|
-
const s = suffixName ? suffixName : 'none'
|
|
530
|
-
return `${baseClass}-with-${p}-${s}`
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
export const getTypeAttributes = (markerType, markerInfo = null) => {
|
|
534
|
-
const { typeInfoByName } = getTypeSeparation()
|
|
535
|
-
const type = typeInfoByName.get(markerType)
|
|
536
|
-
if (!type) {
|
|
537
|
-
return { type: '1', class: 'ol-decimal', suffix: '.' }
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Get prefix/suffix from markerInfo if provided
|
|
541
|
-
const detectedPrefix = markerInfo?.prefix || null
|
|
542
|
-
const detectedSuffix = markerInfo?.suffix || '.'
|
|
543
|
-
|
|
544
|
-
// Standard marker types mapping
|
|
545
|
-
const standardTypes = {
|
|
546
|
-
'decimal': { type: '1', baseClass: 'ol-decimal' },
|
|
547
|
-
'lower-latin': { type: 'a', baseClass: 'ol-lower-latin' },
|
|
548
|
-
'upper-latin': { type: 'A', baseClass: 'ol-upper-latin' },
|
|
549
|
-
'lower-roman': { type: 'i', baseClass: 'ol-lower-roman' },
|
|
550
|
-
'upper-roman': { type: 'I', baseClass: 'ol-upper-roman' }
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Custom marker types with no suffix
|
|
554
|
-
const customTypesNoSuffix = [
|
|
555
|
-
'filled-circled-decimal', 'circled-decimal',
|
|
556
|
-
'circled-upper-latin', 'filled-circled-upper-latin',
|
|
557
|
-
'circled-lower-latin', 'katakana', 'katakana-iroha'
|
|
558
|
-
]
|
|
559
|
-
|
|
560
|
-
let mappedType
|
|
561
|
-
let suffix = detectedSuffix
|
|
562
|
-
let prefix = detectedPrefix
|
|
563
|
-
let customMarker = false
|
|
564
|
-
|
|
565
|
-
if (standardTypes[type.name]) {
|
|
566
|
-
const std = standardTypes[type.name]
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
if (
|
|
594
|
-
result.
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (
|
|
598
|
-
result.
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
spacePattern = pattern.suffix ?
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
//
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
//
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
//
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
})
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
})
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
let
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
if (
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
1
|
+
// ListTypes.json related utilities and marker processing
|
|
2
|
+
|
|
3
|
+
import types from '../listTypes.json' with { type: 'json' }
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if a marker type is convertible in default mode
|
|
7
|
+
* Exotic markers that aren't commonly used are excluded from conversion
|
|
8
|
+
* @param {string} markerType - The marker type name (e.g., 'decimal', 'lower-greek')
|
|
9
|
+
* @returns {boolean} True if the marker type should be converted in default mode
|
|
10
|
+
*/
|
|
11
|
+
export const isConvertibleMarkerType = (markerType) => {
|
|
12
|
+
if (!markerType) return false
|
|
13
|
+
|
|
14
|
+
// Exclude exotic markers that should remain as <ul> in default mode
|
|
15
|
+
// These are rarely used and may not be well-supported
|
|
16
|
+
const excludedTypes = [
|
|
17
|
+
'fullwidth-lower-roman',
|
|
18
|
+
'fullwidth-upper-roman',
|
|
19
|
+
'squared-upper-latin',
|
|
20
|
+
'filled-squared-upper-latin'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
return !excludedTypes.includes(markerType)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const escapeRegExp = (string) => {
|
|
27
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Normalize fullwidth digits to ASCII digits
|
|
31
|
+
const normalizeFullwidthDigits = (str) => {
|
|
32
|
+
if (!str || typeof str !== 'string') return str
|
|
33
|
+
return str.replace(/[0-9]/g, ch => {
|
|
34
|
+
const code = ch.codePointAt(0)
|
|
35
|
+
return String.fromCharCode(code - 0xFF10 + 48)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Resolve top-level pattern groups once at module initialization.
|
|
40
|
+
// Use `patternGroups` top-level key from `listTypes.json`.
|
|
41
|
+
const PATTERN_GROUPS = Array.isArray(types.patternGroups) ? types.patternGroups : []
|
|
42
|
+
|
|
43
|
+
// Map for O(1) name -> group lookup
|
|
44
|
+
const PATTERN_GROUP_MAP = new Map((PATTERN_GROUPS || []).map(g => [g.name, g]))
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get pattern list by name.
|
|
48
|
+
* Accepts a single name (string), an array of names, or a pattern-group-like object.
|
|
49
|
+
*/
|
|
50
|
+
const getPatternsByName = (patternName) => {
|
|
51
|
+
if (!patternName) return []
|
|
52
|
+
// If caller passed the actual group object
|
|
53
|
+
if (typeof patternName === 'object' && Array.isArray(patternName.patterns)) {
|
|
54
|
+
return patternName.patterns
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If caller passed an array of group names, merge their patterns in order
|
|
58
|
+
if (Array.isArray(patternName)) {
|
|
59
|
+
const out = []
|
|
60
|
+
for (const name of patternName) {
|
|
61
|
+
if (typeof name !== 'string') continue
|
|
62
|
+
const g = PATTERN_GROUP_MAP.get(name)
|
|
63
|
+
if (g && Array.isArray(g.patterns)) out.push(...g.patterns)
|
|
64
|
+
}
|
|
65
|
+
return out
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Single name lookup (O(1) via map)
|
|
69
|
+
if (typeof patternName === 'string') {
|
|
70
|
+
const patternGroup = PATTERN_GROUP_MAP.get(patternName)
|
|
71
|
+
return Array.isArray(patternGroup?.patterns) ? patternGroup.patterns : []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return []
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Precompiled common regexes to avoid recreating them repeatedly
|
|
78
|
+
const ROMAN_LIKE_REGEX = /^[IVX]+\.?$/i
|
|
79
|
+
const LATIN_LETTER_REGEX = /^[a-z]$/i
|
|
80
|
+
const MARKER_FALLBACK_REGEX = /^(\S+)/
|
|
81
|
+
const PURE_PREFIX_REMOVAL_REGEX = /^[\((]+/
|
|
82
|
+
|
|
83
|
+
// Build dynamic suffix character classes from `listTypes.json` patterns so
|
|
84
|
+
// edits to that file don't require touching this code.
|
|
85
|
+
const _buildSuffixCharSets = () => {
|
|
86
|
+
const all = new Set()
|
|
87
|
+
const fullwidth = new Set()
|
|
88
|
+
const groups = PATTERN_GROUPS
|
|
89
|
+
if (groups.length > 0) {
|
|
90
|
+
for (const group of groups) {
|
|
91
|
+
if (!group || !Array.isArray(group.patterns)) continue
|
|
92
|
+
for (const p of group.patterns) {
|
|
93
|
+
if (!p || !p.suffix) continue
|
|
94
|
+
for (const ch of Array.from(p.suffix)) {
|
|
95
|
+
all.add(ch)
|
|
96
|
+
const cp = ch.codePointAt(0)
|
|
97
|
+
if (cp !== undefined && cp > 0xFF) fullwidth.add(ch)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { all, fullwidth }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { all: _ALL_SUFFIX_CHARS, fullwidth: _FULLWIDTH_SUFFIX_CHARS } = _buildSuffixCharSets()
|
|
106
|
+
|
|
107
|
+
// Reuse existing `escapeRegExp` for char-class escaping to avoid duplication
|
|
108
|
+
const SUFFIX_CHAR_CLASS = [..._ALL_SUFFIX_CHARS].map(ch => escapeRegExp(ch)).join('')
|
|
109
|
+
|
|
110
|
+
const SUFFIX_CLASS_FOR_REGEX = SUFFIX_CHAR_CLASS.length > 0 ? `[${SUFFIX_CHAR_CLASS}]` : "\\."
|
|
111
|
+
|
|
112
|
+
const MARKER_SUFFIX_SPACE_REGEX = new RegExp(`^([^\\s]+?${SUFFIX_CLASS_FOR_REGEX})\\s`)
|
|
113
|
+
const MARKER_SUFFIX_NO_SPACE_REGEX = new RegExp(`^([^\\s]+?${SUFFIX_CLASS_FOR_REGEX})(?=[^\\s])`)
|
|
114
|
+
const PURE_SUFFIXES_REMOVAL_REGEX = new RegExp(`${SUFFIX_CLASS_FOR_REGEX}+$`)
|
|
115
|
+
|
|
116
|
+
// Cached type separation
|
|
117
|
+
let _symbolBasedTypes = null
|
|
118
|
+
let _rangeBasedTypes = null
|
|
119
|
+
let _sortedSymbolTypes = null
|
|
120
|
+
let _typeInfoByName = null
|
|
121
|
+
|
|
122
|
+
const getTypeSeparation = () => {
|
|
123
|
+
if (_symbolBasedTypes === null) {
|
|
124
|
+
const allTypes = compiledTypes()
|
|
125
|
+
_symbolBasedTypes = []
|
|
126
|
+
_rangeBasedTypes = []
|
|
127
|
+
_typeInfoByName = new Map()
|
|
128
|
+
|
|
129
|
+
// Create typeInfo lookup map
|
|
130
|
+
for (const type of types.types) {
|
|
131
|
+
_typeInfoByName.set(type.name, type)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const compiledType of allTypes) {
|
|
135
|
+
const typeInfo = _typeInfoByName.get(compiledType.name)
|
|
136
|
+
if (typeInfo?.symbols) {
|
|
137
|
+
_symbolBasedTypes.push(compiledType)
|
|
138
|
+
} else {
|
|
139
|
+
_rangeBasedTypes.push(compiledType)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Sort symbol-based types to prioritize Roman numerals over Latin letters
|
|
144
|
+
_sortedSymbolTypes = [..._symbolBasedTypes].sort((a, b) => {
|
|
145
|
+
if (a.name.includes('roman') && b.name.includes('latin')) return -1
|
|
146
|
+
if (a.name.includes('latin') && b.name.includes('roman')) return 1
|
|
147
|
+
return 0
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
symbolBasedTypes: _symbolBasedTypes,
|
|
152
|
+
rangeBasedTypes: _rangeBasedTypes,
|
|
153
|
+
sortedSymbolTypes: _sortedSymbolTypes,
|
|
154
|
+
typeInfoByName: _typeInfoByName
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get start value from type info
|
|
160
|
+
* @param {Object} typeInfo - Type information
|
|
161
|
+
* @returns {number} Start value (default 1)
|
|
162
|
+
*/
|
|
163
|
+
const getStartValue = (typeInfo) => typeInfo?.start !== undefined ? typeInfo.start : 1
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if symbols match a specific sequence pattern
|
|
167
|
+
* @param {Array<string>} pureSymbols - Array of pure symbols (without prefix/suffix)
|
|
168
|
+
* @param {Array<string>} sequence - Expected sequence array
|
|
169
|
+
* @param {string} typeName - Name of the type to return
|
|
170
|
+
* @param {boolean} allSame - Whether all symbols are the same
|
|
171
|
+
* @returns {Object|null} Type object or null
|
|
172
|
+
*/
|
|
173
|
+
const checkSequenceMatch = (pureSymbols, sequence, typeName, allSame) => {
|
|
174
|
+
if (allSame && pureSymbols.length >= 1) {
|
|
175
|
+
if (sequence.indexOf(pureSymbols[0]) !== -1) {
|
|
176
|
+
return { type: typeName }
|
|
177
|
+
}
|
|
178
|
+
} else if (pureSymbols.length >= 2) {
|
|
179
|
+
// Find where the first symbol appears in the sequence
|
|
180
|
+
const startIndex = sequence.indexOf(pureSymbols[0])
|
|
181
|
+
if (startIndex === -1) return null
|
|
182
|
+
|
|
183
|
+
// Check if all symbols match a consecutive part of the sequence
|
|
184
|
+
let isValidSequence = true
|
|
185
|
+
for (let i = 0; i < pureSymbols.length; i++) {
|
|
186
|
+
const expectedIndex = startIndex + i
|
|
187
|
+
if (expectedIndex >= sequence.length || pureSymbols[i] !== sequence[expectedIndex]) {
|
|
188
|
+
isValidSequence = false
|
|
189
|
+
break
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (isValidSequence) {
|
|
193
|
+
return { type: typeName }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Check if array matches expected sequence from start
|
|
201
|
+
* @param {Array<string>} actual - Actual symbols
|
|
202
|
+
* @param {Array<string>} expected - Expected sequence
|
|
203
|
+
* @returns {boolean} True if matches
|
|
204
|
+
*/
|
|
205
|
+
const matchesSequence = (actual, expected) => {
|
|
206
|
+
for (let i = 0; i < actual.length; i++) {
|
|
207
|
+
if (actual[i] !== expected[i]) {
|
|
208
|
+
return false
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Extract pure symbol by removing prefix and suffix
|
|
216
|
+
* @param {string} marker - The marker with prefix/suffix
|
|
217
|
+
* @param {string|null} prefix - The prefix to remove
|
|
218
|
+
* @param {string|null} suffix - The suffix to remove
|
|
219
|
+
* @returns {string} Pure symbol without prefix/suffix
|
|
220
|
+
*/
|
|
221
|
+
const extractPureSymbol = (marker, prefix, suffix) => {
|
|
222
|
+
let start = prefix ? prefix.length : 0
|
|
223
|
+
let end = suffix ? marker.length - suffix.length : marker.length
|
|
224
|
+
return marker.substring(start, end)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Calculate number for a marker based on type info
|
|
229
|
+
* @param {Object} typeInfo - Type information from listTypes.json
|
|
230
|
+
* @param {string} pureSymbol - Pure symbol without prefix/suffix
|
|
231
|
+
* @returns {number|undefined} The calculated number
|
|
232
|
+
*/
|
|
233
|
+
const calculateNumber = (typeInfo, pureSymbol, compiled = null) => {
|
|
234
|
+
if (!typeInfo || !pureSymbol) return undefined
|
|
235
|
+
|
|
236
|
+
if (typeInfo.symbols) {
|
|
237
|
+
// Symbol-based types (katakana, roman numerals, etc.)
|
|
238
|
+
// Prefer a provided compiled object (with symbolIndexMap) to avoid map lookups.
|
|
239
|
+
if (compiled && compiled.symbolIndexMap) {
|
|
240
|
+
const idx = compiled.symbolIndexMap.get(pureSymbol)
|
|
241
|
+
return idx !== undefined ? idx + getStartValue(typeInfo) : undefined
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Fall back to looking up compiled map once (lazy) if compiled not provided
|
|
245
|
+
const compiledFallback = _COMPILED_BY_NAME.get(typeInfo.name)
|
|
246
|
+
if (compiledFallback && compiledFallback.symbolIndexMap) {
|
|
247
|
+
const idx = compiledFallback.symbolIndexMap.get(pureSymbol)
|
|
248
|
+
return idx !== undefined ? idx + getStartValue(typeInfo) : undefined
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const symbolIndex = typeInfo.symbols.indexOf(pureSymbol)
|
|
252
|
+
return symbolIndex !== -1 ? symbolIndex + getStartValue(typeInfo) : undefined
|
|
253
|
+
} else if (typeInfo.range) {
|
|
254
|
+
// Range-based types (lower-latin, upper-latin, decimal)
|
|
255
|
+
const startValue = getStartValue(typeInfo)
|
|
256
|
+
if (Array.isArray(typeInfo.range) && typeof typeInfo.range[0] === 'string') {
|
|
257
|
+
// Latin range: ["a", "z"] or ["A", "Z"]
|
|
258
|
+
const startChar = typeInfo.range[0]
|
|
259
|
+
if (pureSymbol.length > 0) {
|
|
260
|
+
// Use codePointAt for proper Unicode handling (including surrogate pairs)
|
|
261
|
+
const charCode = pureSymbol.codePointAt(0)
|
|
262
|
+
const startCharCode = startChar.codePointAt(0)
|
|
263
|
+
return charCode - startCharCode + startValue
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
// Numeric range: [start, end]
|
|
267
|
+
const norm = normalizeFullwidthDigits(pureSymbol)
|
|
268
|
+
const numValue = parseInt(norm, 10)
|
|
269
|
+
if (!isNaN(numValue)) {
|
|
270
|
+
return numValue
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return undefined
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Detect sequence pattern from multiple contents
|
|
279
|
+
export const detectSequencePattern = (allContents) => {
|
|
280
|
+
if (!allContents || allContents.length < 1) return null
|
|
281
|
+
|
|
282
|
+
// Extract pure markers from all contents
|
|
283
|
+
const markers = allContents.map(content => {
|
|
284
|
+
const trimmed = content.trim()
|
|
285
|
+
// First try: symbol(s) followed by suffix and optional space
|
|
286
|
+
let match = trimmed.match(MARKER_SUFFIX_SPACE_REGEX)
|
|
287
|
+
if (match) return match[1]
|
|
288
|
+
|
|
289
|
+
// Second try: symbol(s) followed by suffix at end (no space after)
|
|
290
|
+
match = trimmed.match(MARKER_SUFFIX_NO_SPACE_REGEX)
|
|
291
|
+
if (match) return match[1]
|
|
292
|
+
|
|
293
|
+
// Fallback: everything before first space
|
|
294
|
+
match = trimmed.match(MARKER_FALLBACK_REGEX)
|
|
295
|
+
return match ? match[1] : trimmed
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
// Extract pure symbols (remove prefixes/suffixes)
|
|
299
|
+
const pureSymbols = markers.map(marker => {
|
|
300
|
+
return marker.replace(PURE_SUFFIXES_REMOVAL_REGEX, '').replace(PURE_PREFIX_REMOVAL_REGEX, '')
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
// Check if all symbols are the same (repeated marker case)
|
|
304
|
+
const allSame = pureSymbols.every(s => s === pureSymbols[0])
|
|
305
|
+
|
|
306
|
+
// Cache type lookups
|
|
307
|
+
const { typeInfoByName } = getTypeSeparation()
|
|
308
|
+
const irohaType = typeInfoByName.get('katakana-iroha')
|
|
309
|
+
const katakanaType = typeInfoByName.get('katakana')
|
|
310
|
+
const upperRomanType = typeInfoByName.get('upper-roman')
|
|
311
|
+
|
|
312
|
+
// Get symbol sequences from listTypes.json
|
|
313
|
+
if (irohaType?.symbols) {
|
|
314
|
+
const irohaResult = checkSequenceMatch(pureSymbols, irohaType.symbols, 'katakana-iroha', allSame)
|
|
315
|
+
if (irohaResult) return irohaResult
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (katakanaType?.symbols) {
|
|
319
|
+
const katakanaResult = checkSequenceMatch(pureSymbols, katakanaType.symbols, 'katakana', allSame)
|
|
320
|
+
if (katakanaResult) return katakanaResult
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Check Roman numerals from listTypes.json
|
|
324
|
+
if (upperRomanType?.symbols) {
|
|
325
|
+
const allRomanLike = markers.every(marker => ROMAN_LIKE_REGEX.test(marker))
|
|
326
|
+
|
|
327
|
+
if (allRomanLike) {
|
|
328
|
+
const romanSymbols = markers.map(marker => marker.replace(/\.$/, '').toUpperCase())
|
|
329
|
+
if (matchesSequence(romanSymbols, upperRomanType.symbols)) {
|
|
330
|
+
return { type: 'upper-roman' }
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Check Latin letters (range-based types)
|
|
336
|
+
const allLatinLike = pureSymbols.every(symbol => LATIN_LETTER_REGEX.test(symbol))
|
|
337
|
+
|
|
338
|
+
if (allLatinLike && pureSymbols.length >= 2) {
|
|
339
|
+
const firstSymbol = pureSymbols[0]
|
|
340
|
+
const isLowerCase = firstSymbol === firstSymbol.toLowerCase()
|
|
341
|
+
const baseCharCode = isLowerCase ? firstSymbol.charCodeAt(0) : firstSymbol.toUpperCase().charCodeAt(0)
|
|
342
|
+
|
|
343
|
+
const expectedSequence = pureSymbols.map((_, i) => {
|
|
344
|
+
const char = String.fromCharCode(baseCharCode + i)
|
|
345
|
+
return isLowerCase ? char : char.toUpperCase()
|
|
346
|
+
})
|
|
347
|
+
const actualSequence = pureSymbols.map(s => isLowerCase ? s : s.toUpperCase())
|
|
348
|
+
|
|
349
|
+
if (matchesSequence(actualSequence, expectedSequence)) {
|
|
350
|
+
return { type: isLowerCase ? 'lower-latin' : 'upper-latin' }
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return null
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Create marker detection result object
|
|
359
|
+
* @param {string} type - Marker type name
|
|
360
|
+
* @param {string} marker - Detected marker
|
|
361
|
+
* @param {number|undefined} number - Calculated number
|
|
362
|
+
* @param {string|null} prefix - Prefix character
|
|
363
|
+
* @param {string|null} suffix - Suffix character
|
|
364
|
+
* @returns {Object} Detection result
|
|
365
|
+
*/
|
|
366
|
+
const createMarkerResult = (type, marker, number, prefix, suffix) => ({
|
|
367
|
+
type,
|
|
368
|
+
marker,
|
|
369
|
+
number,
|
|
370
|
+
prefix,
|
|
371
|
+
suffix
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Try to match content against compiled type patterns
|
|
376
|
+
* @param {string} trimmed - Trimmed content
|
|
377
|
+
* @param {Object} compiledType - Compiled type info
|
|
378
|
+
* @param {Object} typeInfo - Type info from listTypes.json
|
|
379
|
+
* @returns {Object|null} Match result or null
|
|
380
|
+
*/
|
|
381
|
+
const tryMatchPattern = (trimmed, compiledType, typeInfo) => {
|
|
382
|
+
for (const pattern of compiledType.patterns) {
|
|
383
|
+
const m = matchRegexEntry(trimmed, compiledType.name, pattern)
|
|
384
|
+
if (m) return m
|
|
385
|
+
}
|
|
386
|
+
return null
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Enhanced marker type detection with context awareness
|
|
390
|
+
export const detectMarkerType = (content, allContents = null) => {
|
|
391
|
+
if (!content || typeof content !== 'string') {
|
|
392
|
+
return { type: null, marker: null }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const trimmed = content.trim()
|
|
396
|
+
if (!trimmed) return { type: null, marker: null }
|
|
397
|
+
|
|
398
|
+
const { sortedSymbolTypes, rangeBasedTypes, typeInfoByName } = getTypeSeparation()
|
|
399
|
+
|
|
400
|
+
// If we have context (even single element), try to detect the overall pattern first
|
|
401
|
+
if (allContents && Array.isArray(allContents) && allContents.length >= 1) {
|
|
402
|
+
const contextResult = detectSequencePattern(allContents)
|
|
403
|
+
if (contextResult) {
|
|
404
|
+
// If context suggests a specific type, verify current content matches
|
|
405
|
+
const typeInfo = typeInfoByName.get(contextResult.type)
|
|
406
|
+
if (typeInfo) {
|
|
407
|
+
// Check both symbol-based and range-based types
|
|
408
|
+
const allCompiledTypes = [...sortedSymbolTypes, ...rangeBasedTypes]
|
|
409
|
+
for (const compiledType of allCompiledTypes) {
|
|
410
|
+
if (compiledType.name === contextResult.type) {
|
|
411
|
+
const matchResult = tryMatchPattern(trimmed, compiledType, typeInfo)
|
|
412
|
+
if (matchResult) return matchResult
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Fallback to original logic
|
|
420
|
+
// Fast fallback: try a flattened precompiled pattern list to avoid nested loops
|
|
421
|
+
const flatMatch = tryMatchAgainstFlattened(trimmed)
|
|
422
|
+
if (flatMatch) return flatMatch
|
|
423
|
+
|
|
424
|
+
return { type: null, marker: null }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Get the symbol for a specific number in a marker type
|
|
429
|
+
* @param {string} markerType - The marker type name (e.g., 'katakana-iroha', 'lower-roman')
|
|
430
|
+
* @param {number} number - The 1-based number (e.g., 1 for first item, 2 for second)
|
|
431
|
+
* @returns {string|null} The symbol for that number, or null if not found
|
|
432
|
+
*/
|
|
433
|
+
export const getSymbolForNumber = (markerType, number) => {
|
|
434
|
+
const { typeInfoByName } = getTypeSeparation()
|
|
435
|
+
const typeInfo = typeInfoByName.get(markerType)
|
|
436
|
+
if (!typeInfo) {
|
|
437
|
+
return null
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// For symbol-based types
|
|
441
|
+
if (typeInfo.symbols && Array.isArray(typeInfo.symbols)) {
|
|
442
|
+
const startValue = getStartValue(typeInfo)
|
|
443
|
+
const index = number - startValue
|
|
444
|
+
if (index >= 0 && index < typeInfo.symbols.length) {
|
|
445
|
+
return typeInfo.symbols[index]
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// For range-based types (latin, decimal)
|
|
450
|
+
if (typeInfo.range) {
|
|
451
|
+
const startValue = getStartValue(typeInfo)
|
|
452
|
+
if (Array.isArray(typeInfo.range) && typeInfo.range.length === 2) {
|
|
453
|
+
// Latin range: ["a", "z"] or ["A", "Z"]
|
|
454
|
+
const startChar = typeInfo.range[0]
|
|
455
|
+
// Ensure startChar is a string
|
|
456
|
+
if (typeof startChar === 'string' && startChar.length > 0) {
|
|
457
|
+
// Use codePointAt and fromCodePoint for proper Unicode handling
|
|
458
|
+
const startCharCode = startChar.codePointAt(0)
|
|
459
|
+
const offset = number - startValue
|
|
460
|
+
const targetCharCode = startCharCode + offset
|
|
461
|
+
return String.fromCodePoint(targetCharCode)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Numeric range or fallback
|
|
465
|
+
return String(number)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return null
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get the default prefix/suffix pattern for a marker type
|
|
473
|
+
* @param {string} markerType - The marker type name (e.g., 'lower-roman', 'decimal')
|
|
474
|
+
* @returns {Object} Object with prefix and suffix properties
|
|
475
|
+
*/
|
|
476
|
+
export const getDefaultPatternForType = (markerType) => {
|
|
477
|
+
const { typeInfoByName } = getTypeSeparation()
|
|
478
|
+
const typeInfo = typeInfoByName.get(markerType)
|
|
479
|
+
if (!typeInfo) {
|
|
480
|
+
return { prefix: '', suffix: '.' }
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Get patterns for this type (prefer `pattern` property)
|
|
484
|
+
const patternRef = typeInfo.pattern || null
|
|
485
|
+
const patterns = getPatternsByName(patternRef)
|
|
486
|
+
if (!patterns || patterns.length === 0) {
|
|
487
|
+
return { prefix: '', suffix: '.' }
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Return the first pattern as the default
|
|
491
|
+
return {
|
|
492
|
+
prefix: patterns[0].prefix || '',
|
|
493
|
+
suffix: patterns[0].suffix || '.'
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const prefixs = [
|
|
498
|
+
['(', 'round'],
|
|
499
|
+
//['[', 'square'],
|
|
500
|
+
//['{', 'curly'],
|
|
501
|
+
//['<', 'angle'],
|
|
502
|
+
['(', 'fullround'],
|
|
503
|
+
]
|
|
504
|
+
|
|
505
|
+
const suffixs = [
|
|
506
|
+
[')', 'round'],
|
|
507
|
+
//[']', 'square'],
|
|
508
|
+
//['}', 'curly'],
|
|
509
|
+
//['>', 'angle'],
|
|
510
|
+
[')', 'fullround'],
|
|
511
|
+
]
|
|
512
|
+
|
|
513
|
+
// Build Maps for O(1) lookups (faster than .find on every call)
|
|
514
|
+
const prefixMap = new Map(prefixs)
|
|
515
|
+
const suffixMap = new Map(suffixs)
|
|
516
|
+
|
|
517
|
+
const generateClassName = (baseClass, prefix, suffix) => {
|
|
518
|
+
// fast path: no prefix and no suffix
|
|
519
|
+
if (!prefix && !suffix) return baseClass
|
|
520
|
+
|
|
521
|
+
// O(1) map lookups
|
|
522
|
+
const prefixName = prefixMap.get(prefix) || null
|
|
523
|
+
const suffixName = suffixMap.get(suffix) || null
|
|
524
|
+
|
|
525
|
+
// If neither side matches known labels, return baseClass
|
|
526
|
+
if (!prefixName && !suffixName) return baseClass
|
|
527
|
+
|
|
528
|
+
const p = prefixName ? prefixName : 'none'
|
|
529
|
+
const s = suffixName ? suffixName : 'none'
|
|
530
|
+
return `${baseClass}-with-${p}-${s}`
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export const getTypeAttributes = (markerType, markerInfo = null, opt = {}) => {
|
|
534
|
+
const { typeInfoByName } = getTypeSeparation()
|
|
535
|
+
const type = typeInfoByName.get(markerType)
|
|
536
|
+
if (!type) {
|
|
537
|
+
return { type: '1', class: 'ol-decimal', suffix: '.' }
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Get prefix/suffix from markerInfo if provided
|
|
541
|
+
const detectedPrefix = markerInfo?.prefix || null
|
|
542
|
+
const detectedSuffix = markerInfo?.suffix || '.'
|
|
543
|
+
|
|
544
|
+
// Standard marker types mapping
|
|
545
|
+
const standardTypes = {
|
|
546
|
+
'decimal': { type: '1', baseClass: 'ol-decimal' },
|
|
547
|
+
'lower-latin': { type: 'a', baseClass: 'ol-lower-latin' },
|
|
548
|
+
'upper-latin': { type: 'A', baseClass: 'ol-upper-latin' },
|
|
549
|
+
'lower-roman': { type: 'i', baseClass: 'ol-lower-roman' },
|
|
550
|
+
'upper-roman': { type: 'I', baseClass: 'ol-upper-roman' }
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Custom marker types with no suffix
|
|
554
|
+
const customTypesNoSuffix = [
|
|
555
|
+
'filled-circled-decimal', 'circled-decimal',
|
|
556
|
+
'circled-upper-latin', 'filled-circled-upper-latin',
|
|
557
|
+
'circled-lower-latin', 'katakana', 'katakana-iroha'
|
|
558
|
+
]
|
|
559
|
+
|
|
560
|
+
let mappedType
|
|
561
|
+
let suffix = detectedSuffix
|
|
562
|
+
let prefix = detectedPrefix
|
|
563
|
+
let customMarker = false
|
|
564
|
+
|
|
565
|
+
if (standardTypes[type.name]) {
|
|
566
|
+
const std = standardTypes[type.name]
|
|
567
|
+
const baseClass = std.baseClass
|
|
568
|
+
const decoratedClass = opt.addMarkerStyleToClass
|
|
569
|
+
? generateClassName(baseClass, prefix, suffix)
|
|
570
|
+
: baseClass
|
|
571
|
+
mappedType = {
|
|
572
|
+
type: std.type,
|
|
573
|
+
class: decoratedClass
|
|
574
|
+
}
|
|
575
|
+
} else {
|
|
576
|
+
// Custom marker types
|
|
577
|
+
mappedType = { type: '1', class: `ol-${type.name}` }
|
|
578
|
+
customMarker = true
|
|
579
|
+
if (customTypesNoSuffix.includes(type.name)) {
|
|
580
|
+
suffix = null
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const result = {
|
|
585
|
+
type: customMarker ? null : mappedType.type, // No type attribute for custom markers
|
|
586
|
+
class: mappedType.class,
|
|
587
|
+
suffix: suffix,
|
|
588
|
+
customMarker: customMarker,
|
|
589
|
+
start: getStartValue(type)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Set role="list" for custom markers
|
|
593
|
+
if (customMarker) {
|
|
594
|
+
result.role = 'list'
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (prefix) {
|
|
598
|
+
result.prefix = prefix
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (type.symbols) {
|
|
602
|
+
result.symbols = type.symbols
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return result
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
// Precompute regex tail (endCheck + spacePattern) for a pattern to avoid recomputing
|
|
611
|
+
const createPatternTail = (pattern) => {
|
|
612
|
+
// Determine if the pattern's suffix (if any) contains any fullwidth suffix
|
|
613
|
+
// characters declared in `listTypes.json`.
|
|
614
|
+
let isFullWidthSuffix = false
|
|
615
|
+
if (pattern.suffix) {
|
|
616
|
+
for (const ch of Array.from(pattern.suffix)) {
|
|
617
|
+
if (_FULLWIDTH_SUFFIX_CHARS.has(ch)) {
|
|
618
|
+
isFullWidthSuffix = true
|
|
619
|
+
break
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Compute default space handling based on whether suffix exists and
|
|
625
|
+
// whether suffix contains a fullwidth character. This is the fallback
|
|
626
|
+
// behavior when `pattern.space` is not provided.
|
|
627
|
+
const defaultSpaceIfSuffix = isFullWidthSuffix ? '([ ])?' : '([ ])+'
|
|
628
|
+
const defaultSpaceNoSuffix = '(?=[ ]|$)([ ])*'
|
|
629
|
+
|
|
630
|
+
let spacePattern
|
|
631
|
+
|
|
632
|
+
if (pattern.space) {
|
|
633
|
+
// Explicit directive from listTypes.json wins
|
|
634
|
+
switch (pattern.space) {
|
|
635
|
+
case 'half':
|
|
636
|
+
spacePattern = '([ ])+'
|
|
637
|
+
break
|
|
638
|
+
case 'both':
|
|
639
|
+
spacePattern = '([ ])+'
|
|
640
|
+
break
|
|
641
|
+
case 'none_or_both':
|
|
642
|
+
spacePattern = pattern.suffix ? '([ ]+)?' : defaultSpaceNoSuffix
|
|
643
|
+
break
|
|
644
|
+
default:
|
|
645
|
+
// Unknown explicit value: fall back to defaults
|
|
646
|
+
spacePattern = pattern.suffix ? defaultSpaceIfSuffix : defaultSpaceNoSuffix
|
|
647
|
+
}
|
|
648
|
+
} else {
|
|
649
|
+
// No explicit `pattern.space`: use defaults
|
|
650
|
+
spacePattern = pattern.suffix ? defaultSpaceIfSuffix : defaultSpaceNoSuffix
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
let endCheck = ''
|
|
654
|
+
if (!pattern.suffix && pattern.prefix) {
|
|
655
|
+
endCheck = '(?=[ ]|$)'
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return `${endCheck}${spacePattern}`
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Process patterns for symbols
|
|
662
|
+
const processSymbolPatterns = (patterns, symbols, typePatterns, type) => {
|
|
663
|
+
// Pre-compute escaped prefixes, suffixes and regex tail once
|
|
664
|
+
const patternCache = new Map()
|
|
665
|
+
typePatterns.forEach((pattern, index) => {
|
|
666
|
+
const escapedPrefix = pattern.prefix ? escapeRegExp(pattern.prefix) : ''
|
|
667
|
+
const escapedSuffix = pattern.suffix ? escapeRegExp(pattern.suffix) : ''
|
|
668
|
+
const tail = createPatternTail(pattern)
|
|
669
|
+
patternCache.set(index, {
|
|
670
|
+
prefix: pattern.prefix,
|
|
671
|
+
suffix: pattern.suffix,
|
|
672
|
+
space: pattern.space,
|
|
673
|
+
escapedPrefix,
|
|
674
|
+
escapedSuffix,
|
|
675
|
+
tail
|
|
676
|
+
})
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
// Use pre-computed cache for faster pattern generation
|
|
680
|
+
const symbolsLength = symbols.length
|
|
681
|
+
const patternsLength = typePatterns.length
|
|
682
|
+
|
|
683
|
+
for (let symbolIndex = 0; symbolIndex < symbolsLength; symbolIndex++) {
|
|
684
|
+
const sym = symbols[symbolIndex]
|
|
685
|
+
const processedSym = sym.replace(/^\\\\/,'\\')
|
|
686
|
+
|
|
687
|
+
for (let patternIndex = 0; patternIndex < patternsLength; patternIndex++) {
|
|
688
|
+
const cached = patternCache.get(patternIndex)
|
|
689
|
+
// Original suffix variant
|
|
690
|
+
const symbolPartOrig = cached.escapedPrefix + processedSym + cached.escapedSuffix
|
|
691
|
+
const regexStrOrig = `^(${symbolPartOrig})${cached.tail}`
|
|
692
|
+
patterns.push({
|
|
693
|
+
regex: new RegExp(regexStrOrig, 'u'),
|
|
694
|
+
prefix: cached.prefix,
|
|
695
|
+
suffix: cached.suffix,
|
|
696
|
+
symbolIndex,
|
|
697
|
+
num: symbolIndex + type.start
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
// Do not generate additional suffix variants — respect patterns from listTypes.json only.
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Only generate patterns that directly correspond to `typePatterns` entries.
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Process range patterns
|
|
708
|
+
const processRangePatterns = (patterns, typePatterns, type) => {
|
|
709
|
+
// Pre-calculate symbol pattern once
|
|
710
|
+
let symbolPattern
|
|
711
|
+
if (typeof type.range[0] === 'number') {
|
|
712
|
+
// Allow only ASCII digits here — do not synthesize fullwidth digit variants.
|
|
713
|
+
symbolPattern = '(?:\\d+)'
|
|
714
|
+
} else {
|
|
715
|
+
const start = type.range[0].codePointAt(0)
|
|
716
|
+
const end = type.range[1].codePointAt(0)
|
|
717
|
+
|
|
718
|
+
// Handle Unicode characters properly
|
|
719
|
+
if (start > 0xFFFF || end > 0xFFFF) {
|
|
720
|
+
// Unicode characters beyond BMP - enumerate all characters
|
|
721
|
+
const chars = []
|
|
722
|
+
for (let code = start; code <= end; code++) {
|
|
723
|
+
chars.push(String.fromCodePoint(code))
|
|
724
|
+
}
|
|
725
|
+
symbolPattern = `[${chars.join('').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]`
|
|
726
|
+
} else {
|
|
727
|
+
// Standard ASCII/BMP characters
|
|
728
|
+
symbolPattern = `[${String.fromCharCode(start)}-${String.fromCharCode(end)}]`
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Manual loop instead of forEach for better performance
|
|
733
|
+
const patternsLength = typePatterns.length
|
|
734
|
+
for (let i = 0; i < patternsLength; i++) {
|
|
735
|
+
const pattern = typePatterns[i]
|
|
736
|
+
const escapedPrefix = pattern.prefix ? escapeRegExp(pattern.prefix) : ''
|
|
737
|
+
const escapedSuffix = pattern.suffix ? escapeRegExp(pattern.suffix) : ''
|
|
738
|
+
const tail = createPatternTail(pattern)
|
|
739
|
+
|
|
740
|
+
const symbolPart = escapedPrefix + symbolPattern + escapedSuffix
|
|
741
|
+
const regexStr = `^(${symbolPart})${tail}`
|
|
742
|
+
patterns.push({
|
|
743
|
+
regex: new RegExp(regexStr, 'u'),
|
|
744
|
+
prefix: pattern.prefix,
|
|
745
|
+
suffix: pattern.suffix,
|
|
746
|
+
symbolIndex: 0,
|
|
747
|
+
num: type.start,
|
|
748
|
+
isRange: true,
|
|
749
|
+
rangeType: typeof type.range[0] === 'number' ? 'numeric' : 'alphabetic'
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
// Note: Do not synthesize additional suffix variants here.
|
|
753
|
+
// Matching must be driven solely by patterns defined in `listTypes.json`.
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Compiled types with caching
|
|
758
|
+
export const compiledTypes = (() => {
|
|
759
|
+
let _cache = null
|
|
760
|
+
return () => {
|
|
761
|
+
if (_cache === null) {
|
|
762
|
+
_cache = types.types.map(type => {
|
|
763
|
+
const patterns = []
|
|
764
|
+
// Per-type `pattern` references a `patternGroups` entry
|
|
765
|
+
const typePatternRef = type.pattern || null
|
|
766
|
+
const typePatterns = getPatternsByName(typePatternRef)
|
|
767
|
+
|
|
768
|
+
// Build symbol->index map for symbol-based types to avoid indexOf scans
|
|
769
|
+
let symbolIndexMap = null
|
|
770
|
+
if (type.symbols && Array.isArray(type.symbols)) {
|
|
771
|
+
symbolIndexMap = new Map()
|
|
772
|
+
for (let i = 0; i < type.symbols.length; i++) {
|
|
773
|
+
symbolIndexMap.set(type.symbols[i], i)
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (type.symbols) {
|
|
778
|
+
processSymbolPatterns(patterns, type.symbols, typePatterns, type)
|
|
779
|
+
} else if (type.range) {
|
|
780
|
+
processRangePatterns(patterns, typePatterns, type)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return {
|
|
784
|
+
name: type.name,
|
|
785
|
+
patterns,
|
|
786
|
+
symbolIndexMap
|
|
787
|
+
}
|
|
788
|
+
})
|
|
789
|
+
}
|
|
790
|
+
return _cache
|
|
791
|
+
}
|
|
792
|
+
})()
|
|
793
|
+
|
|
794
|
+
// Map of compiled types by name for O(1) lookup
|
|
795
|
+
// Build a map of compiled types by name once for fast lookups
|
|
796
|
+
const _COMPILED_BY_NAME = (() => {
|
|
797
|
+
const m = new Map()
|
|
798
|
+
for (const t of compiledTypes()) m.set(t.name, t)
|
|
799
|
+
return m
|
|
800
|
+
})()
|
|
801
|
+
|
|
802
|
+
export const compiledTypesByName = () => _COMPILED_BY_NAME
|
|
803
|
+
|
|
804
|
+
// Build a flattened pattern list (preserve previous priority: sortedSymbolTypes then rangeBasedTypes)
|
|
805
|
+
const _FLATTENED_PATTERNS = (() => {
|
|
806
|
+
const arr = []
|
|
807
|
+
const { sortedSymbolTypes, rangeBasedTypes } = getTypeSeparation()
|
|
808
|
+
const source = [...sortedSymbolTypes, ...rangeBasedTypes]
|
|
809
|
+
for (const compiledType of source) {
|
|
810
|
+
const compiled = _COMPILED_BY_NAME.get(compiledType.name)
|
|
811
|
+
for (const p of compiledType.patterns) {
|
|
812
|
+
arr.push({
|
|
813
|
+
regex: p.regex,
|
|
814
|
+
prefix: p.prefix,
|
|
815
|
+
suffix: p.suffix,
|
|
816
|
+
typeName: compiledType.name,
|
|
817
|
+
symbolIndex: p.symbolIndex,
|
|
818
|
+
num: p.num,
|
|
819
|
+
isRange: p.isRange,
|
|
820
|
+
compiled: compiled || null
|
|
821
|
+
})
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return arr
|
|
825
|
+
})()
|
|
826
|
+
|
|
827
|
+
// Fast matcher over flattened list
|
|
828
|
+
const tryMatchAgainstFlattened = (trimmed) => {
|
|
829
|
+
for (const entry of _FLATTENED_PATTERNS) {
|
|
830
|
+
const m = matchRegexEntry(trimmed, entry.typeName, entry)
|
|
831
|
+
if (m) return m
|
|
832
|
+
}
|
|
833
|
+
return null
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Helper to match a trimmed string against a pattern entry and produce a result.
|
|
837
|
+
// `entry` is expected to have `regex`, `prefix`, `suffix` and optionally `compiled`.
|
|
838
|
+
const matchRegexEntry = (trimmed, typeName, entry) => {
|
|
839
|
+
const result = trimmed.match(entry.regex)
|
|
840
|
+
if (!result) return null
|
|
841
|
+
|
|
842
|
+
const detectedMarker = result[1]
|
|
843
|
+
const pureSymbol = extractPureSymbol(detectedMarker, entry.prefix, entry.suffix)
|
|
844
|
+
const { typeInfoByName } = getTypeSeparation()
|
|
845
|
+
const typeInfo = typeInfoByName.get(typeName)
|
|
846
|
+
const compiledForCalc = entry.compiled || _COMPILED_BY_NAME.get(typeName)
|
|
847
|
+
const number = calculateNumber(typeInfo, pureSymbol, compiledForCalc)
|
|
848
|
+
|
|
849
|
+
return createMarkerResult(typeName, detectedMarker, number, entry.prefix, entry.suffix)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Analyze list context to determine optimal marker type for ambiguous cases
|
|
853
|
+
export const analyzeListMarkerContext = (markerInfos) => {
|
|
854
|
+
if (!markerInfos || markerInfos.length === 0) return markerInfos
|
|
855
|
+
|
|
856
|
+
const { symbolBasedTypes, typeInfoByName } = getTypeSeparation()
|
|
857
|
+
|
|
858
|
+
// Create typeInfo lookup cache
|
|
859
|
+
const typeInfoCache = new Map()
|
|
860
|
+
for (const compiledType of symbolBasedTypes) {
|
|
861
|
+
const typeInfo = typeInfoByName.get(compiledType.name)
|
|
862
|
+
if (typeInfo?.symbols) {
|
|
863
|
+
typeInfoCache.set(compiledType.name, typeInfo)
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Group markers by possible types
|
|
868
|
+
const candidateTypes = new Map()
|
|
869
|
+
|
|
870
|
+
markerInfos.forEach((markerInfo, index) => {
|
|
871
|
+
if (!markerInfo.marker) return
|
|
872
|
+
|
|
873
|
+
// Extract the actual symbol without prefix/suffix
|
|
874
|
+
const actualSymbol = extractPureSymbol(markerInfo.marker, markerInfo.prefix, markerInfo.suffix)
|
|
875
|
+
|
|
876
|
+
// Find all possible types for this marker
|
|
877
|
+
const possibleTypes = []
|
|
878
|
+
for (const [typeName, typeInfo] of typeInfoCache) {
|
|
879
|
+
let symbolIndex = -1
|
|
880
|
+
const compiled = _COMPILED_BY_NAME.get(typeName)
|
|
881
|
+
if (compiled && compiled.symbolIndexMap) {
|
|
882
|
+
const idx = compiled.symbolIndexMap.get(actualSymbol)
|
|
883
|
+
symbolIndex = idx !== undefined ? idx : -1
|
|
884
|
+
} else {
|
|
885
|
+
symbolIndex = typeInfo.symbols.indexOf(actualSymbol)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (symbolIndex !== -1) {
|
|
889
|
+
const expectedNumber = symbolIndex + getStartValue(typeInfo)
|
|
890
|
+
|
|
891
|
+
possibleTypes.push({
|
|
892
|
+
typeName,
|
|
893
|
+
symbolIndex,
|
|
894
|
+
expectedNumber,
|
|
895
|
+
actualPosition: index + 1
|
|
896
|
+
})
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
possibleTypes.forEach(pt => {
|
|
901
|
+
if (!candidateTypes.has(pt.typeName)) {
|
|
902
|
+
candidateTypes.set(pt.typeName, {
|
|
903
|
+
matches: 0,
|
|
904
|
+
totalItems: markerInfos.length,
|
|
905
|
+
positions: []
|
|
906
|
+
})
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const candidate = candidateTypes.get(pt.typeName)
|
|
910
|
+
candidate.matches++
|
|
911
|
+
candidate.positions.push({
|
|
912
|
+
index,
|
|
913
|
+
expectedNumber: pt.expectedNumber,
|
|
914
|
+
actualPosition: pt.actualPosition,
|
|
915
|
+
marker: markerInfo.marker
|
|
916
|
+
})
|
|
917
|
+
})
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
// Score each candidate type
|
|
921
|
+
let bestType = null
|
|
922
|
+
let bestScore = -1
|
|
923
|
+
|
|
924
|
+
for (const [typeName, candidate] of candidateTypes) {
|
|
925
|
+
let score = 0
|
|
926
|
+
|
|
927
|
+
// Check if positions form a consecutive sequence starting from 1
|
|
928
|
+
candidate.positions.sort((a, b) => a.index - b.index)
|
|
929
|
+
let isConsecutiveFrom1 = true
|
|
930
|
+
let expectedStart = 1
|
|
931
|
+
|
|
932
|
+
for (let i = 0; i < candidate.positions.length; i++) {
|
|
933
|
+
const pos = candidate.positions[i]
|
|
934
|
+
if (pos.expectedNumber !== expectedStart + i) {
|
|
935
|
+
isConsecutiveFrom1 = false
|
|
936
|
+
break
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Higher score for consecutive sequences starting from 1
|
|
941
|
+
if (isConsecutiveFrom1 && candidate.positions.length > 0 && candidate.positions[0].expectedNumber === 1) {
|
|
942
|
+
score += 100
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Higher score for more matches
|
|
946
|
+
score += candidate.matches * 10
|
|
947
|
+
|
|
948
|
+
// Higher score for covering all items
|
|
949
|
+
if (candidate.matches === candidate.totalItems) {
|
|
950
|
+
score += 50
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (score > bestScore) {
|
|
954
|
+
bestScore = score
|
|
955
|
+
bestType = typeName
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// If we found a better type, update all marker infos
|
|
960
|
+
if (bestType && candidateTypes.get(bestType).matches > 0) {
|
|
961
|
+
const typeInfo = typeInfoCache.get(bestType)
|
|
962
|
+
if (typeInfo) {
|
|
963
|
+
const updatedMarkerInfos = markerInfos.map((markerInfo, index) => {
|
|
964
|
+
if (!markerInfo.marker) return markerInfo
|
|
965
|
+
|
|
966
|
+
// Extract the actual symbol without prefix/suffix
|
|
967
|
+
const actualSymbol = extractPureSymbol(markerInfo.marker, markerInfo.prefix, markerInfo.suffix)
|
|
968
|
+
|
|
969
|
+
// Use precomputed symbolIndexMap if available
|
|
970
|
+
const compiled = _COMPILED_BY_NAME.get(bestType)
|
|
971
|
+
let symbolIndex = -1
|
|
972
|
+
if (compiled && compiled.symbolIndexMap) {
|
|
973
|
+
const idx = compiled.symbolIndexMap.get(actualSymbol)
|
|
974
|
+
symbolIndex = idx !== undefined ? idx : -1
|
|
975
|
+
} else {
|
|
976
|
+
symbolIndex = typeInfo.symbols.indexOf(actualSymbol)
|
|
977
|
+
}
|
|
978
|
+
if (symbolIndex !== -1) {
|
|
979
|
+
const number = calculateNumber(typeInfo, actualSymbol)
|
|
980
|
+
|
|
981
|
+
return {
|
|
982
|
+
...markerInfo,
|
|
983
|
+
type: bestType,
|
|
984
|
+
number: number
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return markerInfo
|
|
988
|
+
})
|
|
989
|
+
|
|
990
|
+
return updatedMarkerInfos
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return markerInfos
|
|
995
|
+
}
|