-
Notifications
You must be signed in to change notification settings - Fork 4
/
string.js
135 lines (116 loc) · 4.8 KB
/
string.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
const REGEXP_INDENT_LINE = /\n/g
const indentLine = (string, indentString = ' ', indentStringStart = indentString) => `${indentStringStart}${string.replace(REGEXP_INDENT_LINE, `\n${indentString}`)}`
const indentList = (
title,
itemList = [],
indentStringStart = ' - ',
indentString = ' '.repeat(indentStringStart.length)
) => [
title,
...itemList.map((item) => indentLine(item, indentString, indentStringStart))
].join('\n')
const autoEllipsis = (string = '', limit = 64, head = 32, tail = 16) => string.length > limit
? `${string.slice(0, head)}...${tail > 0 ? string.slice(-tail) : ''} (+${string.length - head - tail})`
: string
const REGEXP_SPLIT_CAMEL_CASE_TEST = /[^A-Z]/
const REGEXP_SPLIT_CAMEL_CASE = /([^A-Z]|[A-Z]{2,})([A-Z])/g
const splitCamelCase = (string) => REGEXP_SPLIT_CAMEL_CASE_TEST.test(string) ? string.replace(REGEXP_SPLIT_CAMEL_CASE, '$1 $2').split(' ') : [ string ]
const splitSnakeCase = (string) => string.split('_')
const splitKebabCase = (string) => string.split('-')
const joinCamelCase = (stringList, fromIndex = 1) => stringList.reduce((o, string, index) => o + (index < fromIndex ? string : capFirst(string)), '')
const joinSnakeCase = (stringList) => stringList.join('_').toUpperCase() // SCREAMING_SNAKE_CASE
const joinKebabCase = (stringList) => stringList.join('-').toLowerCase()
const capFirst = (string) => string.charAt(0).toUpperCase() + string.slice(1)
// pass in valueMap like { 'name': 'NAME' } and `the name is {name}` will be `the name is NAME`
const createMarkReplacer = (valueMap = {}) => {
const REGEXP_KEY = /[\w-]+/
const markMap = {}
for (const [ key, value ] of Object.entries(valueMap)) {
if (!REGEXP_KEY.test(key)) throw new Error(`invalid key: ${key}`)
if (value === undefined) continue // allow undefined, but not other value
if (typeof (value) !== 'string') throw new Error(`invalid value: ${value} of key: ${key}`)
markMap[ `{${key}}` ] = value
}
const REGEXP_MARK = /{[\w-]+}/g
const replacerFunc = (mark) => markMap[ mark ] || mark
return (string) => string.replace(REGEXP_MARK, replacerFunc)
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
// https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
// https://lodash.com/docs#escapeRegExp
const REGEXP_ESCAPE_REGEXP = /[\\^$.*+?()[\]{}|]/g
const escapeRegExp = (string) => string.replace(REGEXP_ESCAPE_REGEXP, '\\$&')
if (__DEV__) { // code to generate
const ESCAPE_HTML_MAP = {}
const UNESCAPE_HTML_MAP = {}
const ESCAPE_LIST = []
const UNESCAPE_LIST = []
const BASIC_HTML_ESCAPE_LIST = [
'" #34 quot',
'& #38 amp',
// apos may not be usable in HTML4 Browser, check:
// https://stackoverflow.com/questions/9187946/escaping-inside-html-tag-attribute-value
// https://stackoverflow.com/questions/2083754/why-shouldnt-apos-be-used-to-escape-single-quotes
'\' #39 apos',
'< #60 lt',
'> #62 gt'
]
BASIC_HTML_ESCAPE_LIST.forEach((value) => {
const [ char, decimal, named ] = value.split(' ')
const decimalEntity = `&${decimal};`
const namedEntity = `&${named};`
ESCAPE_HTML_MAP[ char ] = decimalEntity
UNESCAPE_HTML_MAP[ decimalEntity ] = char
UNESCAPE_HTML_MAP[ namedEntity ] = char
ESCAPE_LIST.push(char)
UNESCAPE_LIST.push(decimal, named)
})
const REGEXP_ESCAPE_HTML = new RegExp(`[${ESCAPE_LIST.join('')}]`, 'g')
const REGEXP_UNESCAPE_HTML = new RegExp(`&(?:${UNESCAPE_LIST.join('|')});`, 'g')
console.log({
ESCAPE_HTML_MAP,
UNESCAPE_HTML_MAP,
REGEXP_ESCAPE_HTML,
REGEXP_UNESCAPE_HTML
})
}
const ESCAPE_HTML_MAP = {
'"': '"',
'&': '&',
"'": ''',
'<': '<',
'>': '>'
}
const REGEXP_ESCAPE_HTML = /["&'<>]/g
const replaceEscapeHTML = (string) => ESCAPE_HTML_MAP[ string ] || string
const escapeHTML = (string) => string && string.replace(REGEXP_ESCAPE_HTML, replaceEscapeHTML)
const UNESCAPE_HTML_MAP = {
'"': '"',
'"': '"',
'&': '&',
'&': '&',
''': "'",
''': "'",
'<': '<',
'<': '<',
'>': '>',
'>': '>'
}
const REGEXP_UNESCAPE_HTML = /&(?:#34|quot|#38|amp|#39|apos|#60|lt|#62|gt);/g
const replaceUnescapeHTML = (string) => UNESCAPE_HTML_MAP[ string ] || string
const unescapeHTML = (string) => string && string.replace(REGEXP_UNESCAPE_HTML, replaceUnescapeHTML)
// remove XML invalid Char
const REGEXP_INVALID_CHAR_XML = /[^\x09\x0A\x0D\x20-\xFF\x85\xA0-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]/gm // eslint-disable-line no-control-regex
const removeInvalidCharXML = (string) => string.replace(REGEXP_INVALID_CHAR_XML, '')
export {
indentLine,
indentList,
autoEllipsis,
splitCamelCase, joinCamelCase,
splitSnakeCase, joinSnakeCase,
splitKebabCase, joinKebabCase,
createMarkReplacer,
escapeRegExp,
escapeHTML, unescapeHTML,
removeInvalidCharXML
}