@hpcc-js/util 3.4.6 → 3.4.8
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/LICENSE +43 -43
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +1 -1
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +4 -3
- package/src/__package__.ts +3 -3
- package/src/array.ts +97 -97
- package/src/cache.ts +65 -65
- package/src/debounce.ts +88 -88
- package/src/dictionary.ts +69 -69
- package/src/dispatch.ts +125 -125
- package/src/esp.ts +32 -32
- package/src/graph.ts +353 -353
- package/src/graph2.ts +675 -675
- package/src/hashSum.ts +55 -55
- package/src/immutable.ts +156 -156
- package/src/index.ts +21 -21
- package/src/logging.ts +212 -212
- package/src/math.ts +92 -92
- package/src/object.ts +122 -122
- package/src/observer.ts +91 -91
- package/src/platform.ts +20 -20
- package/src/saxParser.ts +135 -135
- package/src/stack.ts +41 -41
- package/src/stateful.ts +178 -178
- package/src/string.ts +21 -21
- package/src/url.ts +27 -27
- package/src/utf8ToBase64.ts +47 -47
package/src/object.ts
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
export type RecursivePartial<T> = {
|
|
2
|
-
[P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] :
|
|
3
|
-
T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P];
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* inner - return inner property of Object
|
|
8
|
-
* Usage: inner("some.prop.to.locate", obj);
|
|
9
|
-
*
|
|
10
|
-
* @param prop - property to locate
|
|
11
|
-
* @param obj - object to locate property in
|
|
12
|
-
*/
|
|
13
|
-
export function inner(prop: string, obj: any): any {
|
|
14
|
-
if (prop === void 0 || obj === void 0) return void 0;
|
|
15
|
-
for (const item of prop.split(".")) {
|
|
16
|
-
if (!obj.hasOwnProperty(item)) {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
obj = obj[item];
|
|
20
|
-
}
|
|
21
|
-
return obj;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* exists - return true if inner property of Object exists
|
|
26
|
-
* Usage: exists("some.prop.to.locate", obj);
|
|
27
|
-
*
|
|
28
|
-
* @param prop - property to locate
|
|
29
|
-
* @param obj - object to locate property in
|
|
30
|
-
*/
|
|
31
|
-
export function exists(prop: string, obj: any): boolean {
|
|
32
|
-
return inner(prop, obj) !== undefined;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function _mixin(dest: any, source: any): any {
|
|
36
|
-
const empty: any = {};
|
|
37
|
-
for (const key in source) {
|
|
38
|
-
if (!source.hasOwnProperty(key)) continue;
|
|
39
|
-
if (key === "__proto__" || key === "constructor") continue;
|
|
40
|
-
let s: any = source[key];
|
|
41
|
-
if (s instanceof Array) {
|
|
42
|
-
// TODO: Do we need to support arrays?
|
|
43
|
-
} else if (typeof s === "object") {
|
|
44
|
-
s = deepMixin(dest[key], s);
|
|
45
|
-
}
|
|
46
|
-
if (!(key in dest) || (dest[key] !== s && (!(key in empty) || empty[key] !== s))) {
|
|
47
|
-
dest[key] = s;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return dest;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* deepMixin - combine several objects from right to left
|
|
55
|
-
* Usage: deepMixin({a: "a"}, {b: "b"});
|
|
56
|
-
*
|
|
57
|
-
* @param dest - target object to mix into.
|
|
58
|
-
* @param sources - objects to mix in
|
|
59
|
-
*/
|
|
60
|
-
export function deepMixin(dest: any = {}, ...sources: any[]): any {
|
|
61
|
-
if (typeof dest !== "object") throw new Error(`Destination "${dest}" must be an object.`);
|
|
62
|
-
for (const source of sources) {
|
|
63
|
-
_mixin(dest, source);
|
|
64
|
-
}
|
|
65
|
-
return dest;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* deepMixinT - combine several objects of Partial<T> from right to left
|
|
70
|
-
* Usage: deepMixinT<MyInterface>({a: "a"}, {b: "b"});
|
|
71
|
-
*
|
|
72
|
-
* Note: Only provided as a convenience, so user gets auto completion based on destination type.
|
|
73
|
-
*
|
|
74
|
-
* @param dest - target object to mix into.
|
|
75
|
-
* @param sources - objects to mix in
|
|
76
|
-
*/
|
|
77
|
-
export function deepMixinT<T>(dest: RecursivePartial<T> = {}, ...sources: Array<RecursivePartial<T>>): T {
|
|
78
|
-
return deepMixin(dest, ...sources);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* safeStingify - JSONsimilar to .stringify, except ignores circular references.
|
|
83
|
-
* Usage: safeStingify(object);
|
|
84
|
-
*
|
|
85
|
-
* @param obj - any object.
|
|
86
|
-
*/
|
|
87
|
-
export function safeStringify(obj: object) {
|
|
88
|
-
const cache: any[] = [];
|
|
89
|
-
return JSON.stringify(obj, function (key, value) {
|
|
90
|
-
if (typeof value === "object" && value !== null) {
|
|
91
|
-
if (cache.indexOf(value) !== -1) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
cache.push(value);
|
|
96
|
-
}
|
|
97
|
-
return value;
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function isArray(arg: any): arg is any[] {
|
|
102
|
-
if (Array.isArray !== undefined) {
|
|
103
|
-
return Array.isArray(arg);
|
|
104
|
-
}
|
|
105
|
-
return Object.prototype.toString.call(arg) === "[object Array]";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface ClassMeta {
|
|
109
|
-
module: string;
|
|
110
|
-
file: string;
|
|
111
|
-
class: string;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function classID2Meta(classID: string): ClassMeta {
|
|
115
|
-
const info = classID.split("_");
|
|
116
|
-
const classInfo = info[1].split(".");
|
|
117
|
-
return {
|
|
118
|
-
module: `@hpcc-js/${info[0]}`,
|
|
119
|
-
file: classInfo[0],
|
|
120
|
-
class: classInfo[1] || classInfo[0]
|
|
121
|
-
};
|
|
122
|
-
}
|
|
1
|
+
export type RecursivePartial<T> = {
|
|
2
|
+
[P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] :
|
|
3
|
+
T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P];
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* inner - return inner property of Object
|
|
8
|
+
* Usage: inner("some.prop.to.locate", obj);
|
|
9
|
+
*
|
|
10
|
+
* @param prop - property to locate
|
|
11
|
+
* @param obj - object to locate property in
|
|
12
|
+
*/
|
|
13
|
+
export function inner(prop: string, obj: any): any {
|
|
14
|
+
if (prop === void 0 || obj === void 0) return void 0;
|
|
15
|
+
for (const item of prop.split(".")) {
|
|
16
|
+
if (!obj.hasOwnProperty(item)) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
obj = obj[item];
|
|
20
|
+
}
|
|
21
|
+
return obj;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* exists - return true if inner property of Object exists
|
|
26
|
+
* Usage: exists("some.prop.to.locate", obj);
|
|
27
|
+
*
|
|
28
|
+
* @param prop - property to locate
|
|
29
|
+
* @param obj - object to locate property in
|
|
30
|
+
*/
|
|
31
|
+
export function exists(prop: string, obj: any): boolean {
|
|
32
|
+
return inner(prop, obj) !== undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function _mixin(dest: any, source: any): any {
|
|
36
|
+
const empty: any = {};
|
|
37
|
+
for (const key in source) {
|
|
38
|
+
if (!source.hasOwnProperty(key)) continue;
|
|
39
|
+
if (key === "__proto__" || key === "constructor") continue;
|
|
40
|
+
let s: any = source[key];
|
|
41
|
+
if (s instanceof Array) {
|
|
42
|
+
// TODO: Do we need to support arrays?
|
|
43
|
+
} else if (typeof s === "object") {
|
|
44
|
+
s = deepMixin(dest[key], s);
|
|
45
|
+
}
|
|
46
|
+
if (!(key in dest) || (dest[key] !== s && (!(key in empty) || empty[key] !== s))) {
|
|
47
|
+
dest[key] = s;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return dest;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* deepMixin - combine several objects from right to left
|
|
55
|
+
* Usage: deepMixin({a: "a"}, {b: "b"});
|
|
56
|
+
*
|
|
57
|
+
* @param dest - target object to mix into.
|
|
58
|
+
* @param sources - objects to mix in
|
|
59
|
+
*/
|
|
60
|
+
export function deepMixin(dest: any = {}, ...sources: any[]): any {
|
|
61
|
+
if (typeof dest !== "object") throw new Error(`Destination "${dest}" must be an object.`);
|
|
62
|
+
for (const source of sources) {
|
|
63
|
+
_mixin(dest, source);
|
|
64
|
+
}
|
|
65
|
+
return dest;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* deepMixinT - combine several objects of Partial<T> from right to left
|
|
70
|
+
* Usage: deepMixinT<MyInterface>({a: "a"}, {b: "b"});
|
|
71
|
+
*
|
|
72
|
+
* Note: Only provided as a convenience, so user gets auto completion based on destination type.
|
|
73
|
+
*
|
|
74
|
+
* @param dest - target object to mix into.
|
|
75
|
+
* @param sources - objects to mix in
|
|
76
|
+
*/
|
|
77
|
+
export function deepMixinT<T>(dest: RecursivePartial<T> = {}, ...sources: Array<RecursivePartial<T>>): T {
|
|
78
|
+
return deepMixin(dest, ...sources);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* safeStingify - JSONsimilar to .stringify, except ignores circular references.
|
|
83
|
+
* Usage: safeStingify(object);
|
|
84
|
+
*
|
|
85
|
+
* @param obj - any object.
|
|
86
|
+
*/
|
|
87
|
+
export function safeStringify(obj: object) {
|
|
88
|
+
const cache: any[] = [];
|
|
89
|
+
return JSON.stringify(obj, function (key, value) {
|
|
90
|
+
if (typeof value === "object" && value !== null) {
|
|
91
|
+
if (cache.indexOf(value) !== -1) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
cache.push(value);
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isArray(arg: any): arg is any[] {
|
|
102
|
+
if (Array.isArray !== undefined) {
|
|
103
|
+
return Array.isArray(arg);
|
|
104
|
+
}
|
|
105
|
+
return Object.prototype.toString.call(arg) === "[object Array]";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ClassMeta {
|
|
109
|
+
module: string;
|
|
110
|
+
file: string;
|
|
111
|
+
class: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function classID2Meta(classID: string): ClassMeta {
|
|
115
|
+
const info = classID.split("_");
|
|
116
|
+
const classInfo = info[1].split(".");
|
|
117
|
+
return {
|
|
118
|
+
module: `@hpcc-js/${info[0]}`,
|
|
119
|
+
file: classInfo[0],
|
|
120
|
+
class: classInfo[1] || classInfo[0]
|
|
121
|
+
};
|
|
122
|
+
}
|
package/src/observer.ts
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IObserverHandle - Reference to an observing instance
|
|
3
|
-
*/
|
|
4
|
-
export interface IObserverHandle {
|
|
5
|
-
release(): void;
|
|
6
|
-
unwatch(): void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type CallbackFunction = (...args: any[]) => void;
|
|
10
|
-
|
|
11
|
-
class ObserverHandle<T extends string> implements IObserverHandle {
|
|
12
|
-
private eventTarget: Observable<T>;
|
|
13
|
-
private eventID: T;
|
|
14
|
-
private callback: CallbackFunction;
|
|
15
|
-
|
|
16
|
-
constructor(eventTarget: Observable<T>, eventID: T, callback: CallbackFunction) {
|
|
17
|
-
this.eventTarget = eventTarget;
|
|
18
|
-
this.eventID = eventID;
|
|
19
|
-
this.callback = callback;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
release() {
|
|
23
|
-
this.eventTarget.removeObserver(this.eventID, this.callback);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
unwatch() {
|
|
27
|
-
this.release();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type EventID = string;
|
|
32
|
-
export class Observable<T extends EventID> {
|
|
33
|
-
private _eventObservers: { [eventID: string]: CallbackFunction[] } = {};
|
|
34
|
-
|
|
35
|
-
constructor(...events: T[]) {
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
addObserver(eventID: T, callback: CallbackFunction): IObserverHandle {
|
|
39
|
-
let eventObservers: CallbackFunction[] = this._eventObservers[eventID];
|
|
40
|
-
if (!eventObservers) {
|
|
41
|
-
eventObservers = [];
|
|
42
|
-
this._eventObservers[eventID] = eventObservers;
|
|
43
|
-
}
|
|
44
|
-
eventObservers.push(callback);
|
|
45
|
-
return new ObserverHandle<T>(this, eventID, callback);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
removeObserver(eventID: T, callback: CallbackFunction): this {
|
|
49
|
-
const eventObservers = this._eventObservers[eventID];
|
|
50
|
-
if (eventObservers) {
|
|
51
|
-
for (let i = eventObservers.length - 1; i >= 0; --i) {
|
|
52
|
-
if (eventObservers[i] === callback) {
|
|
53
|
-
eventObservers.splice(i, 1);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return this;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
dispatchEvent(eventID: T, ...args: any[]): this {
|
|
61
|
-
const eventObservers = this._eventObservers[eventID];
|
|
62
|
-
if (eventObservers) {
|
|
63
|
-
for (const observer of eventObservers) {
|
|
64
|
-
observer(...args);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return this;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private _hasObserver(eventID: string): boolean {
|
|
71
|
-
const eventObservers = this._eventObservers[eventID];
|
|
72
|
-
for (const observer in eventObservers) {
|
|
73
|
-
if (eventObservers[observer]) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
hasObserver(_eventID?: T): boolean {
|
|
81
|
-
if (_eventID !== void 0) {
|
|
82
|
-
return this._hasObserver(_eventID);
|
|
83
|
-
}
|
|
84
|
-
for (const eventID in this._eventObservers) {
|
|
85
|
-
if (this._hasObserver(eventID)) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* IObserverHandle - Reference to an observing instance
|
|
3
|
+
*/
|
|
4
|
+
export interface IObserverHandle {
|
|
5
|
+
release(): void;
|
|
6
|
+
unwatch(): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type CallbackFunction = (...args: any[]) => void;
|
|
10
|
+
|
|
11
|
+
class ObserverHandle<T extends string> implements IObserverHandle {
|
|
12
|
+
private eventTarget: Observable<T>;
|
|
13
|
+
private eventID: T;
|
|
14
|
+
private callback: CallbackFunction;
|
|
15
|
+
|
|
16
|
+
constructor(eventTarget: Observable<T>, eventID: T, callback: CallbackFunction) {
|
|
17
|
+
this.eventTarget = eventTarget;
|
|
18
|
+
this.eventID = eventID;
|
|
19
|
+
this.callback = callback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
release() {
|
|
23
|
+
this.eventTarget.removeObserver(this.eventID, this.callback);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
unwatch() {
|
|
27
|
+
this.release();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type EventID = string;
|
|
32
|
+
export class Observable<T extends EventID> {
|
|
33
|
+
private _eventObservers: { [eventID: string]: CallbackFunction[] } = {};
|
|
34
|
+
|
|
35
|
+
constructor(...events: T[]) {
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
addObserver(eventID: T, callback: CallbackFunction): IObserverHandle {
|
|
39
|
+
let eventObservers: CallbackFunction[] = this._eventObservers[eventID];
|
|
40
|
+
if (!eventObservers) {
|
|
41
|
+
eventObservers = [];
|
|
42
|
+
this._eventObservers[eventID] = eventObservers;
|
|
43
|
+
}
|
|
44
|
+
eventObservers.push(callback);
|
|
45
|
+
return new ObserverHandle<T>(this, eventID, callback);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
removeObserver(eventID: T, callback: CallbackFunction): this {
|
|
49
|
+
const eventObservers = this._eventObservers[eventID];
|
|
50
|
+
if (eventObservers) {
|
|
51
|
+
for (let i = eventObservers.length - 1; i >= 0; --i) {
|
|
52
|
+
if (eventObservers[i] === callback) {
|
|
53
|
+
eventObservers.splice(i, 1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
dispatchEvent(eventID: T, ...args: any[]): this {
|
|
61
|
+
const eventObservers = this._eventObservers[eventID];
|
|
62
|
+
if (eventObservers) {
|
|
63
|
+
for (const observer of eventObservers) {
|
|
64
|
+
observer(...args);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private _hasObserver(eventID: string): boolean {
|
|
71
|
+
const eventObservers = this._eventObservers[eventID];
|
|
72
|
+
for (const observer in eventObservers) {
|
|
73
|
+
if (eventObservers[observer]) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
hasObserver(_eventID?: T): boolean {
|
|
81
|
+
if (_eventID !== void 0) {
|
|
82
|
+
return this._hasObserver(_eventID);
|
|
83
|
+
}
|
|
84
|
+
for (const eventID in this._eventObservers) {
|
|
85
|
+
if (this._hasObserver(eventID)) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/platform.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
declare const process: any;
|
|
2
|
-
|
|
3
|
-
export const root: any = typeof globalThis !== "undefined" ? globalThis : window;
|
|
4
|
-
export const isBrowser: boolean = typeof window !== "undefined" && root === window;
|
|
5
|
-
export const isNode: boolean = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
6
|
-
export const isCI: boolean = isNode && process.env != null && (process.env.TRAVIS != null || process.env.GITHUB_ACTIONS != null || process.env.CI != null);
|
|
7
|
-
|
|
8
|
-
export function getScriptSrc(partial: string) {
|
|
9
|
-
const scripts = document.scripts || [];
|
|
10
|
-
for (let i = document.scripts.length - 1; i >= 0; --i) {
|
|
11
|
-
const script = scripts[i];
|
|
12
|
-
if (script.src) {
|
|
13
|
-
const idx = script.src.indexOf(partial);
|
|
14
|
-
if (idx >= 0) {
|
|
15
|
-
return script.src.substring(0, idx);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return "";
|
|
20
|
-
}
|
|
1
|
+
declare const process: any;
|
|
2
|
+
|
|
3
|
+
export const root: any = typeof globalThis !== "undefined" ? globalThis : window;
|
|
4
|
+
export const isBrowser: boolean = typeof window !== "undefined" && root === window;
|
|
5
|
+
export const isNode: boolean = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
6
|
+
export const isCI: boolean = isNode && process.env != null && (process.env.TRAVIS != null || process.env.GITHUB_ACTIONS != null || process.env.CI != null);
|
|
7
|
+
|
|
8
|
+
export function getScriptSrc(partial: string) {
|
|
9
|
+
const scripts = document.scripts || [];
|
|
10
|
+
for (let i = document.scripts.length - 1; i >= 0; --i) {
|
|
11
|
+
const script = scripts[i];
|
|
12
|
+
if (script.src) {
|
|
13
|
+
const idx = script.src.indexOf(partial);
|
|
14
|
+
if (idx >= 0) {
|
|
15
|
+
return script.src.substring(0, idx);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return "";
|
|
20
|
+
}
|