@diia-inhouse/diia-logger 4.5.8 → 4.7.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/dist/config.js +3 -0
- package/dist/redactors/fullName.js +51 -29
- package/dist/redactors/phone.js +3 -5
- package/package.json +1 -1
- package/src/config.ts +3 -0
- package/src/redactors/fullName.ts +74 -51
- package/src/redactors/phone.ts +3 -5
package/dist/config.js
CHANGED
|
@@ -66,6 +66,7 @@ const defaultOptions = {
|
|
|
66
66
|
"firstNameUA",
|
|
67
67
|
"lastNameUA",
|
|
68
68
|
"middleNameUA",
|
|
69
|
+
"fullNameUA",
|
|
69
70
|
"serialNumber",
|
|
70
71
|
"department",
|
|
71
72
|
"departmentUA",
|
|
@@ -75,6 +76,8 @@ const defaultOptions = {
|
|
|
75
76
|
"recordNumber",
|
|
76
77
|
"firstNameEN",
|
|
77
78
|
"lastNameEN",
|
|
79
|
+
"middleNameEN",
|
|
80
|
+
"fullNameEN",
|
|
78
81
|
"docNumber",
|
|
79
82
|
"documentRegistrationPlaceUA",
|
|
80
83
|
"currentRegistrationPlaceUA",
|
|
@@ -1,57 +1,79 @@
|
|
|
1
1
|
//#region src/redactors/fullName.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
const minParts = 2;
|
|
3
|
+
const maxParts = 6;
|
|
4
|
+
const nameSeparators = new Set([
|
|
5
|
+
"'",
|
|
6
|
+
"’",
|
|
7
|
+
"-"
|
|
8
|
+
]);
|
|
9
|
+
function isCasedLetter(char) {
|
|
10
|
+
return char.toLowerCase() !== char.toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
function isUpperCaseLetter(char) {
|
|
13
|
+
return isCasedLetter(char) && char === char.toUpperCase();
|
|
14
|
+
}
|
|
15
|
+
function isLowerCaseLetter(char) {
|
|
16
|
+
return isCasedLetter(char) && char === char.toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
function isNameChar(char) {
|
|
19
|
+
return isCasedLetter(char) || nameSeparators.has(char);
|
|
20
|
+
}
|
|
21
|
+
function splitSegments(word) {
|
|
22
|
+
const segments = [];
|
|
23
|
+
let current = "";
|
|
24
|
+
for (const char of word) if (nameSeparators.has(char)) {
|
|
25
|
+
segments.push(current);
|
|
26
|
+
current = "";
|
|
27
|
+
} else current += char;
|
|
28
|
+
segments.push(current);
|
|
29
|
+
return segments;
|
|
30
|
+
}
|
|
31
|
+
function isTitleCaseSegment(segment) {
|
|
32
|
+
if (!isUpperCaseLetter(segment[0])) return false;
|
|
33
|
+
for (let i = 1; i < segment.length; i++) if (!isLowerCaseLetter(segment[i])) return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function isUpperCaseSegment(segment) {
|
|
37
|
+
for (const char of segment) if (!isUpperCaseLetter(char)) return false;
|
|
38
|
+
return true;
|
|
7
39
|
}
|
|
8
40
|
function findNextWordBoundary(text, startIndex) {
|
|
9
|
-
for (let i = startIndex; i < text.length; i++) if (text[i]
|
|
41
|
+
for (let i = startIndex; i < text.length; i++) if (!isNameChar(text[i])) return i;
|
|
10
42
|
return text.length;
|
|
11
43
|
}
|
|
12
|
-
function isUpperCase(char) {
|
|
13
|
-
return checkIsLowercaseLetter(char?.toLowerCase()) && char === char?.toUpperCase();
|
|
14
|
-
}
|
|
15
44
|
function isNamePart(word) {
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (checkIsLowercaseLetter(char) || char === "-" || prevChar === "-" && checkIsLowercaseLetter(char?.toLowerCase())) return true;
|
|
21
|
-
prevChar = char;
|
|
22
|
-
}
|
|
23
|
-
return false;
|
|
45
|
+
if (word.length < 2) return false;
|
|
46
|
+
const segments = splitSegments(word);
|
|
47
|
+
if (segments.some((segment) => segment === "")) return false;
|
|
48
|
+
return segments.every((segment) => isTitleCaseSegment(segment)) || segments.every((segment) => isUpperCaseSegment(segment));
|
|
24
49
|
}
|
|
25
50
|
function extractFullName(text, startIndex) {
|
|
26
51
|
const parts = [];
|
|
27
52
|
let currentIndex = startIndex;
|
|
28
|
-
|
|
53
|
+
let endIndex = startIndex;
|
|
54
|
+
while (currentIndex < text.length && parts.length < maxParts) {
|
|
29
55
|
const wordEnd = findNextWordBoundary(text, currentIndex);
|
|
30
56
|
const word = text.slice(currentIndex, wordEnd);
|
|
31
57
|
if (!isNamePart(word)) break;
|
|
32
58
|
parts.push(word);
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
currentIndex++;
|
|
37
|
-
} else break;
|
|
59
|
+
endIndex = wordEnd;
|
|
60
|
+
if (text[wordEnd] !== " ") break;
|
|
61
|
+
currentIndex = wordEnd + 1;
|
|
38
62
|
}
|
|
39
|
-
if (parts.length >=
|
|
63
|
+
if (parts.length >= minParts) return [parts.join(" "), endIndex];
|
|
40
64
|
return [void 0, startIndex];
|
|
41
65
|
}
|
|
42
66
|
function nameToInitials(fullName) {
|
|
43
|
-
return fullName.split(
|
|
67
|
+
return fullName.split(" ").map((part) => `${splitSegments(part)[0][0]}.`).join("");
|
|
44
68
|
}
|
|
45
69
|
function redactFullName(text) {
|
|
46
70
|
let result = "";
|
|
47
71
|
let currentIndex = 0;
|
|
48
72
|
while (currentIndex < text.length) {
|
|
49
|
-
|
|
50
|
-
if (isUpperCase(currentChar)) {
|
|
73
|
+
if (isUpperCaseLetter(text[currentIndex])) {
|
|
51
74
|
const [fullName, endIndex] = extractFullName(text, currentIndex);
|
|
52
75
|
if (fullName) {
|
|
53
|
-
|
|
54
|
-
result += `[Fullname redacted: ${initials}] `;
|
|
76
|
+
result += `[Fullname redacted: ${nameToInitials(fullName)}]`;
|
|
55
77
|
currentIndex = endIndex;
|
|
56
78
|
continue;
|
|
57
79
|
}
|
package/dist/redactors/phone.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
//#region src/redactors/phone.ts
|
|
2
2
|
const PHONE_CANDIDATE = /\+?\d[\d\s().-]{7,18}\d/g;
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const minDigits = 10;
|
|
4
|
+
const maxDigits = 15;
|
|
5
5
|
const subscriberDigits = 7;
|
|
6
6
|
function redactPhone(text) {
|
|
7
7
|
return text.replace(PHONE_CANDIDATE, (match) => maskPhone(match));
|
|
8
8
|
}
|
|
9
9
|
function maskPhone(match) {
|
|
10
10
|
const digits = match.replace(/\D/g, "");
|
|
11
|
-
|
|
12
|
-
const isInternational = digits.length === internationalDigits;
|
|
13
|
-
if (!isNational && !isInternational) return match;
|
|
11
|
+
if (digits.length < minDigits || digits.length > maxDigits) return match;
|
|
14
12
|
return `${match.startsWith("+") ? "+" : ""}${digits.slice(0, -subscriberDigits)}${"*".repeat(subscriberDigits - 1)}${digits.slice(-1)}`;
|
|
15
13
|
}
|
|
16
14
|
//#endregion
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -65,6 +65,7 @@ export const defaultOptions: LoggerOptions = {
|
|
|
65
65
|
'firstNameUA',
|
|
66
66
|
'lastNameUA',
|
|
67
67
|
'middleNameUA',
|
|
68
|
+
'fullNameUA',
|
|
68
69
|
'serialNumber',
|
|
69
70
|
'department',
|
|
70
71
|
'departmentUA',
|
|
@@ -74,6 +75,8 @@ export const defaultOptions: LoggerOptions = {
|
|
|
74
75
|
'recordNumber',
|
|
75
76
|
'firstNameEN',
|
|
76
77
|
'lastNameEN',
|
|
78
|
+
'middleNameEN',
|
|
79
|
+
'fullNameEN',
|
|
77
80
|
'docNumber',
|
|
78
81
|
'documentRegistrationPlaceUA',
|
|
79
82
|
'currentRegistrationPlaceUA',
|
|
@@ -1,28 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const minParts = 2
|
|
2
|
+
const maxParts = 6
|
|
3
|
+
|
|
4
|
+
const nameSeparators = new Set(["'", '’', '-'])
|
|
5
|
+
|
|
6
|
+
function isCasedLetter(char: string): boolean {
|
|
7
|
+
return char.toLowerCase() !== char.toUpperCase()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isUpperCaseLetter(char: string): boolean {
|
|
11
|
+
return isCasedLetter(char) && char === char.toUpperCase()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isLowerCaseLetter(char: string): boolean {
|
|
15
|
+
return isCasedLetter(char) && char === char.toLowerCase()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isNameChar(char: string): boolean {
|
|
19
|
+
return isCasedLetter(char) || nameSeparators.has(char)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function splitSegments(word: string): string[] {
|
|
23
|
+
const segments: string[] = []
|
|
24
|
+
let current = ''
|
|
25
|
+
|
|
26
|
+
for (const char of word) {
|
|
27
|
+
if (nameSeparators.has(char)) {
|
|
28
|
+
segments.push(current)
|
|
29
|
+
current = ''
|
|
30
|
+
} else {
|
|
31
|
+
current += char
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
segments.push(current)
|
|
36
|
+
|
|
37
|
+
return segments
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isTitleCaseSegment(segment: string): boolean {
|
|
41
|
+
if (!isUpperCaseLetter(segment[0])) {
|
|
4
42
|
return false
|
|
5
43
|
}
|
|
6
44
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
45
|
+
for (let i = 1; i < segment.length; i++) {
|
|
46
|
+
if (!isLowerCaseLetter(segment[i])) {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
10
49
|
}
|
|
11
50
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isUpperCaseSegment(segment: string): boolean {
|
|
55
|
+
for (const char of segment) {
|
|
56
|
+
if (!isUpperCaseLetter(char)) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
18
60
|
|
|
19
|
-
return
|
|
61
|
+
return true
|
|
20
62
|
}
|
|
21
63
|
|
|
22
64
|
function findNextWordBoundary(text: string, startIndex: number): number {
|
|
23
65
|
for (let i = startIndex; i < text.length; i++) {
|
|
24
|
-
|
|
25
|
-
if (char === ' ') {
|
|
66
|
+
if (!isNameChar(text[i])) {
|
|
26
67
|
return i
|
|
27
68
|
}
|
|
28
69
|
}
|
|
@@ -30,35 +71,25 @@ function findNextWordBoundary(text: string, startIndex: number): number {
|
|
|
30
71
|
return text.length
|
|
31
72
|
}
|
|
32
73
|
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return isLetter && char === char?.toUpperCase()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isNamePart(word: string | undefined): boolean {
|
|
40
|
-
if (!isUpperCase(word?.[0])) {
|
|
74
|
+
function isNamePart(word: string): boolean {
|
|
75
|
+
if (word.length < 2) {
|
|
41
76
|
return false
|
|
42
77
|
}
|
|
43
78
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (checkIsLowercaseLetter(char) || char === '-' || (prevChar === '-' && checkIsLowercaseLetter(char?.toLowerCase()))) {
|
|
48
|
-
return true
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
prevChar = char
|
|
79
|
+
const segments = splitSegments(word)
|
|
80
|
+
if (segments.some((segment) => segment === '')) {
|
|
81
|
+
return false
|
|
52
82
|
}
|
|
53
83
|
|
|
54
|
-
return
|
|
84
|
+
return segments.every((segment) => isTitleCaseSegment(segment)) || segments.every((segment) => isUpperCaseSegment(segment))
|
|
55
85
|
}
|
|
56
86
|
|
|
57
87
|
function extractFullName(text: string, startIndex: number): [string | undefined, number] {
|
|
58
88
|
const parts: string[] = []
|
|
59
89
|
let currentIndex = startIndex
|
|
90
|
+
let endIndex = startIndex
|
|
60
91
|
|
|
61
|
-
while (currentIndex < text.length) {
|
|
92
|
+
while (currentIndex < text.length && parts.length < maxParts) {
|
|
62
93
|
const wordEnd = findNextWordBoundary(text, currentIndex)
|
|
63
94
|
const word = text.slice(currentIndex, wordEnd)
|
|
64
95
|
|
|
@@ -67,22 +98,17 @@ function extractFullName(text: string, startIndex: number): [string | undefined,
|
|
|
67
98
|
}
|
|
68
99
|
|
|
69
100
|
parts.push(word)
|
|
101
|
+
endIndex = wordEnd
|
|
70
102
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (currentIndex < text.length && text[currentIndex] === ' ') {
|
|
74
|
-
if (parts.length >= 6) {
|
|
75
|
-
break
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
currentIndex++
|
|
79
|
-
} else {
|
|
103
|
+
if (text[wordEnd] !== ' ') {
|
|
80
104
|
break
|
|
81
105
|
}
|
|
106
|
+
|
|
107
|
+
currentIndex = wordEnd + 1
|
|
82
108
|
}
|
|
83
109
|
|
|
84
|
-
if (parts.length >=
|
|
85
|
-
return [parts.join(' '),
|
|
110
|
+
if (parts.length >= minParts) {
|
|
111
|
+
return [parts.join(' '), endIndex]
|
|
86
112
|
}
|
|
87
113
|
|
|
88
114
|
return [undefined, startIndex]
|
|
@@ -90,8 +116,8 @@ function extractFullName(text: string, startIndex: number): [string | undefined,
|
|
|
90
116
|
|
|
91
117
|
function nameToInitials(fullName: string): string {
|
|
92
118
|
return fullName
|
|
93
|
-
.split(
|
|
94
|
-
.map((part) => `${part
|
|
119
|
+
.split(' ')
|
|
120
|
+
.map((part) => `${splitSegments(part)[0][0]}.`)
|
|
95
121
|
.join('')
|
|
96
122
|
}
|
|
97
123
|
|
|
@@ -100,14 +126,11 @@ export function redactFullName(text: string): string {
|
|
|
100
126
|
let currentIndex = 0
|
|
101
127
|
|
|
102
128
|
while (currentIndex < text.length) {
|
|
103
|
-
|
|
104
|
-
if (isUpperCase(currentChar)) {
|
|
129
|
+
if (isUpperCaseLetter(text[currentIndex])) {
|
|
105
130
|
const [fullName, endIndex] = extractFullName(text, currentIndex)
|
|
106
131
|
|
|
107
132
|
if (fullName) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
result += `[Fullname redacted: ${initials}] `
|
|
133
|
+
result += `[Fullname redacted: ${nameToInitials(fullName)}]`
|
|
111
134
|
currentIndex = endIndex
|
|
112
135
|
continue
|
|
113
136
|
}
|
package/src/redactors/phone.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// parentheses, dots and dashes. Validated by digit count in maskPhone.
|
|
3
3
|
const PHONE_CANDIDATE = /\+?\d[\d\s().-]{7,18}\d/g
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const minDigits = 10
|
|
6
|
+
const maxDigits = 15
|
|
7
7
|
const subscriberDigits = 7
|
|
8
8
|
|
|
9
9
|
export function redactPhone(text: string): string {
|
|
@@ -12,9 +12,7 @@ export function redactPhone(text: string): string {
|
|
|
12
12
|
|
|
13
13
|
function maskPhone(match: string): string {
|
|
14
14
|
const digits = match.replace(/\D/g, '')
|
|
15
|
-
|
|
16
|
-
const isInternational = digits.length === internationalDigits
|
|
17
|
-
if (!isNational && !isInternational) {
|
|
15
|
+
if (digits.length < minDigits || digits.length > maxDigits) {
|
|
18
16
|
return match
|
|
19
17
|
}
|
|
20
18
|
|