@matdata/yasqe 4.6.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/CHANGELOG.md +121 -0
- package/build/ts/grammar/tokenizer.d.ts +37 -0
- package/build/ts/src/CodeMirror.d.ts +21 -0
- package/build/ts/src/autocompleters/classes.d.ts +3 -0
- package/build/ts/src/autocompleters/index.d.ts +39 -0
- package/build/ts/src/autocompleters/prefixes.d.ts +3 -0
- package/build/ts/src/autocompleters/properties.d.ts +3 -0
- package/build/ts/src/autocompleters/variables.d.ts +3 -0
- package/build/ts/src/defaults.d.ts +75 -0
- package/build/ts/src/imgs.d.ts +7 -0
- package/build/ts/src/index.d.ts +303 -0
- package/build/ts/src/prefixFold.d.ts +8 -0
- package/build/ts/src/prefixUtils.d.ts +9 -0
- package/build/ts/src/sparql.d.ts +20 -0
- package/build/ts/src/tokenUtils.d.ts +4 -0
- package/build/ts/src/tooltip.d.ts +2 -0
- package/build/ts/src/trie.d.ts +13 -0
- package/build/yasqe.html +108 -0
- package/build/yasqe.min.css +2 -0
- package/build/yasqe.min.css.map +1 -0
- package/build/yasqe.min.js +3 -0
- package/build/yasqe.min.js.LICENSE.txt +3 -0
- package/build/yasqe.min.js.map +1 -0
- package/grammar/README.md +12 -0
- package/grammar/_tokenizer-table.js +4776 -0
- package/grammar/build.sh +2 -0
- package/grammar/sparql11-grammar.pl +834 -0
- package/grammar/sparqljs-browser-min.js +4535 -0
- package/grammar/tokenizer.ts +729 -0
- package/grammar/util/gen_ll1.pl +37 -0
- package/grammar/util/gen_sparql11.pl +11 -0
- package/grammar/util/ll1.pl +175 -0
- package/grammar/util/output_to_javascript.pl +75 -0
- package/grammar/util/prune.pl +49 -0
- package/grammar/util/rewrite.pl +104 -0
- package/package.json +40 -0
- package/src/CodeMirror.ts +54 -0
- package/src/autocompleters/classes.ts +32 -0
- package/src/autocompleters/index.ts +346 -0
- package/src/autocompleters/prefixes.ts +130 -0
- package/src/autocompleters/properties.ts +28 -0
- package/src/autocompleters/show-hint.scss +38 -0
- package/src/autocompleters/variables.ts +52 -0
- package/src/defaults.ts +149 -0
- package/src/imgs.ts +14 -0
- package/src/index.ts +1089 -0
- package/src/prefixFold.ts +93 -0
- package/src/prefixUtils.ts +65 -0
- package/src/scss/buttons.scss +275 -0
- package/src/scss/codemirrorMods.scss +36 -0
- package/src/scss/yasqe.scss +89 -0
- package/src/sparql.ts +215 -0
- package/src/tokenUtils.ts +121 -0
- package/src/tooltip.ts +31 -0
- package/src/trie.ts +238 -0
package/src/sparql.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { default as Yasqe, Config, RequestConfig } from "./";
|
|
2
|
+
import { merge, isFunction } from "lodash-es";
|
|
3
|
+
import * as queryString from "query-string";
|
|
4
|
+
export type YasqeAjaxConfig = Config["requestConfig"];
|
|
5
|
+
export interface PopulatedAjaxConfig {
|
|
6
|
+
url: string;
|
|
7
|
+
reqMethod: "POST" | "GET";
|
|
8
|
+
headers: { [key: string]: string };
|
|
9
|
+
accept: string;
|
|
10
|
+
args: RequestArgs;
|
|
11
|
+
withCredentials: boolean;
|
|
12
|
+
}
|
|
13
|
+
function getRequestConfigSettings(yasqe: Yasqe, conf?: Partial<Config["requestConfig"]>): RequestConfig<Yasqe> {
|
|
14
|
+
if (isFunction(conf)) {
|
|
15
|
+
return conf(yasqe) as RequestConfig<Yasqe>;
|
|
16
|
+
}
|
|
17
|
+
return (conf ?? {}) as RequestConfig<Yasqe>;
|
|
18
|
+
}
|
|
19
|
+
// type callback = AjaxConfig.callbacks['complete'];
|
|
20
|
+
export function getAjaxConfig(
|
|
21
|
+
yasqe: Yasqe,
|
|
22
|
+
_config: Partial<Config["requestConfig"]> = {},
|
|
23
|
+
): PopulatedAjaxConfig | undefined {
|
|
24
|
+
const config: RequestConfig<Yasqe> = merge(
|
|
25
|
+
{},
|
|
26
|
+
getRequestConfigSettings(yasqe, yasqe.config.requestConfig),
|
|
27
|
+
getRequestConfigSettings(yasqe, _config),
|
|
28
|
+
);
|
|
29
|
+
if (!config.endpoint || config.endpoint.length == 0) return; // nothing to query!
|
|
30
|
+
|
|
31
|
+
var queryMode = yasqe.getQueryMode();
|
|
32
|
+
/**
|
|
33
|
+
* initialize ajax config
|
|
34
|
+
*/
|
|
35
|
+
const endpoint = isFunction(config.endpoint) ? config.endpoint(yasqe) : config.endpoint;
|
|
36
|
+
var reqMethod: "GET" | "POST" =
|
|
37
|
+
queryMode == "update" ? "POST" : isFunction(config.method) ? config.method(yasqe) : config.method;
|
|
38
|
+
const headers = isFunction(config.headers) ? config.headers(yasqe) : config.headers;
|
|
39
|
+
// console.log({headers})
|
|
40
|
+
const withCredentials = isFunction(config.withCredentials) ? config.withCredentials(yasqe) : config.withCredentials;
|
|
41
|
+
return {
|
|
42
|
+
reqMethod,
|
|
43
|
+
url: endpoint,
|
|
44
|
+
args: getUrlArguments(yasqe, config),
|
|
45
|
+
headers: headers,
|
|
46
|
+
accept: getAcceptHeader(yasqe, config),
|
|
47
|
+
withCredentials,
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* merge additional request headers
|
|
51
|
+
*/
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function executeQuery(yasqe: Yasqe, config?: YasqeAjaxConfig): Promise<any> {
|
|
55
|
+
const queryStart = Date.now();
|
|
56
|
+
try {
|
|
57
|
+
yasqe.emit("queryBefore", yasqe, config);
|
|
58
|
+
const populatedConfig = getAjaxConfig(yasqe, config);
|
|
59
|
+
if (!populatedConfig) {
|
|
60
|
+
return; // Nothing to query
|
|
61
|
+
}
|
|
62
|
+
const abortController = new AbortController();
|
|
63
|
+
|
|
64
|
+
const fetchOptions: RequestInit = {
|
|
65
|
+
method: populatedConfig.reqMethod,
|
|
66
|
+
headers: {
|
|
67
|
+
Accept: populatedConfig.accept,
|
|
68
|
+
...(populatedConfig.headers || {}),
|
|
69
|
+
},
|
|
70
|
+
credentials: populatedConfig.withCredentials ? "include" : "same-origin",
|
|
71
|
+
signal: abortController.signal,
|
|
72
|
+
};
|
|
73
|
+
if (fetchOptions?.headers && populatedConfig.reqMethod === "POST") {
|
|
74
|
+
(fetchOptions.headers as Record<string, string>)["Content-Type"] = "application/x-www-form-urlencoded";
|
|
75
|
+
}
|
|
76
|
+
const searchParams = new URLSearchParams();
|
|
77
|
+
for (const key in populatedConfig.args) {
|
|
78
|
+
const value = populatedConfig.args[key];
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
value.forEach((v) => searchParams.append(key, v));
|
|
81
|
+
} else {
|
|
82
|
+
searchParams.append(key, value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (populatedConfig.reqMethod === "POST") {
|
|
86
|
+
fetchOptions.body = searchParams.toString();
|
|
87
|
+
} else {
|
|
88
|
+
const url = new URL(populatedConfig.url);
|
|
89
|
+
searchParams.forEach((value, key) => {
|
|
90
|
+
url.searchParams.append(key, value);
|
|
91
|
+
});
|
|
92
|
+
populatedConfig.url = url.toString();
|
|
93
|
+
}
|
|
94
|
+
const request = new Request(populatedConfig.url, fetchOptions);
|
|
95
|
+
yasqe.emit("query", request, abortController);
|
|
96
|
+
const response = await fetch(request);
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new Error((await response.text()) || response.statusText);
|
|
99
|
+
}
|
|
100
|
+
// Await the response content and merge with the `Response` object
|
|
101
|
+
const queryResponse = {
|
|
102
|
+
ok: response.ok,
|
|
103
|
+
status: response.status,
|
|
104
|
+
statusText: response.statusText,
|
|
105
|
+
headers: response.headers,
|
|
106
|
+
type: response.type,
|
|
107
|
+
content: await response.text(),
|
|
108
|
+
};
|
|
109
|
+
yasqe.emit("queryResponse", queryResponse, Date.now() - queryStart);
|
|
110
|
+
yasqe.emit("queryResults", queryResponse.content, Date.now() - queryStart);
|
|
111
|
+
return queryResponse;
|
|
112
|
+
} catch (e) {
|
|
113
|
+
if (e instanceof Error && e.message === "Aborted") {
|
|
114
|
+
// The query was aborted. We should not do or draw anything
|
|
115
|
+
} else {
|
|
116
|
+
yasqe.emit("queryResponse", e, Date.now() - queryStart);
|
|
117
|
+
}
|
|
118
|
+
yasqe.emit("error", e);
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type RequestArgs = { [argName: string]: string | string[] };
|
|
124
|
+
export function getUrlArguments(yasqe: Yasqe, _config: Config["requestConfig"]): RequestArgs {
|
|
125
|
+
var queryMode = yasqe.getQueryMode();
|
|
126
|
+
|
|
127
|
+
var data: RequestArgs = {};
|
|
128
|
+
const config: RequestConfig<Yasqe> = getRequestConfigSettings(yasqe, _config);
|
|
129
|
+
var queryArg = isFunction(config.queryArgument) ? config.queryArgument(yasqe) : config.queryArgument;
|
|
130
|
+
if (!queryArg) queryArg = yasqe.getQueryMode();
|
|
131
|
+
data[queryArg] = config.adjustQueryBeforeRequest ? config.adjustQueryBeforeRequest(yasqe) : yasqe.getValue();
|
|
132
|
+
/**
|
|
133
|
+
* add named graphs to ajax config
|
|
134
|
+
*/
|
|
135
|
+
const namedGraphs = isFunction(config.namedGraphs) ? config.namedGraphs(yasqe) : config.namedGraphs;
|
|
136
|
+
if (namedGraphs && namedGraphs.length > 0) {
|
|
137
|
+
let argName = queryMode === "query" ? "named-graph-uri" : "using-named-graph-uri ";
|
|
138
|
+
data[argName] = namedGraphs;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* add default graphs to ajax config
|
|
142
|
+
*/
|
|
143
|
+
const defaultGraphs = isFunction(config.defaultGraphs) ? config.defaultGraphs(yasqe) : config.defaultGraphs;
|
|
144
|
+
if (defaultGraphs && defaultGraphs.length > 0) {
|
|
145
|
+
let argName = queryMode == "query" ? "default-graph-uri" : "using-graph-uri ";
|
|
146
|
+
data[argName] = namedGraphs;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* add additional request args
|
|
151
|
+
*/
|
|
152
|
+
const args = isFunction(config.args) ? config.args(yasqe) : config.args;
|
|
153
|
+
if (args && args.length > 0)
|
|
154
|
+
merge(
|
|
155
|
+
data,
|
|
156
|
+
args.reduce((argsObject: { [key: string]: string[] }, arg) => {
|
|
157
|
+
argsObject[arg.name] ? argsObject[arg.name].push(arg.value) : (argsObject[arg.name] = [arg.value]);
|
|
158
|
+
return argsObject;
|
|
159
|
+
}, {}),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return data;
|
|
163
|
+
}
|
|
164
|
+
export function getAcceptHeader(yasqe: Yasqe, _config: Config["requestConfig"]) {
|
|
165
|
+
const config: RequestConfig<Yasqe> = getRequestConfigSettings(yasqe, _config);
|
|
166
|
+
var acceptHeader = null;
|
|
167
|
+
if (yasqe.getQueryMode() == "update") {
|
|
168
|
+
acceptHeader = isFunction(config.acceptHeaderUpdate) ? config.acceptHeaderUpdate(yasqe) : config.acceptHeaderUpdate;
|
|
169
|
+
} else {
|
|
170
|
+
var qType = yasqe.getQueryType();
|
|
171
|
+
if (qType == "DESCRIBE" || qType == "CONSTRUCT") {
|
|
172
|
+
acceptHeader = isFunction(config.acceptHeaderGraph) ? config.acceptHeaderGraph(yasqe) : config.acceptHeaderGraph;
|
|
173
|
+
} else {
|
|
174
|
+
acceptHeader = isFunction(config.acceptHeaderSelect)
|
|
175
|
+
? config.acceptHeaderSelect(yasqe)
|
|
176
|
+
: config.acceptHeaderSelect;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return acceptHeader;
|
|
180
|
+
}
|
|
181
|
+
export function getAsCurlString(yasqe: Yasqe, _config?: Config["requestConfig"]) {
|
|
182
|
+
let ajaxConfig = getAjaxConfig(yasqe, getRequestConfigSettings(yasqe, _config));
|
|
183
|
+
if (!ajaxConfig) return "";
|
|
184
|
+
let url = ajaxConfig.url;
|
|
185
|
+
if (ajaxConfig.url.indexOf("http") !== 0) {
|
|
186
|
+
//this is either a relative or absolute url, which is not supported by CURL.
|
|
187
|
+
//Add domain, schema, etc etc
|
|
188
|
+
url = `${window.location.protocol}//${window.location.host}`;
|
|
189
|
+
if (ajaxConfig.url.indexOf("/") === 0) {
|
|
190
|
+
//its an absolute path
|
|
191
|
+
url += ajaxConfig.url;
|
|
192
|
+
} else {
|
|
193
|
+
//relative, so append current location to url first
|
|
194
|
+
url += window.location.pathname + ajaxConfig.url;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const segments: string[] = ["curl"];
|
|
198
|
+
|
|
199
|
+
if (ajaxConfig.reqMethod === "GET") {
|
|
200
|
+
url += `?${queryString.stringify(ajaxConfig.args)}`;
|
|
201
|
+
segments.push(url);
|
|
202
|
+
} else if (ajaxConfig.reqMethod === "POST") {
|
|
203
|
+
segments.push(url);
|
|
204
|
+
segments.push("--data", queryString.stringify(ajaxConfig.args));
|
|
205
|
+
} else {
|
|
206
|
+
// I don't expect to get here but let's be sure
|
|
207
|
+
console.warn("Unexpected request-method", ajaxConfig.reqMethod);
|
|
208
|
+
segments.push(url);
|
|
209
|
+
}
|
|
210
|
+
segments.push("-X", ajaxConfig.reqMethod);
|
|
211
|
+
for (const header in ajaxConfig.headers) {
|
|
212
|
+
segments.push(`-H '${header}: ${ajaxConfig.headers[header]}'`);
|
|
213
|
+
}
|
|
214
|
+
return segments.join(" ");
|
|
215
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { default as Yasqe, Token, Position } from "./";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* When typing a query, this query is sometimes syntactically invalid, causing
|
|
5
|
+
* the current tokens to be incorrect This causes problem for autocompletion.
|
|
6
|
+
* http://bla might result in two tokens: http:// and bla. We'll want to combine
|
|
7
|
+
* these
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function getCompleteToken(yasqe: Yasqe, token?: Token, cur?: Position): Token {
|
|
11
|
+
if (!cur) {
|
|
12
|
+
cur = yasqe.getDoc().getCursor() as Position;
|
|
13
|
+
}
|
|
14
|
+
if (!token) {
|
|
15
|
+
token = yasqe.getTokenAt(cur) as Token;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return expandTokenToEnd(yasqe, expandTokenToStart(yasqe, token, cur), cur);
|
|
19
|
+
}
|
|
20
|
+
function expandTokenToStart(yasqe: Yasqe, token: Token, cur: Position): Token {
|
|
21
|
+
var prevToken: Token = yasqe.getTokenAt({
|
|
22
|
+
line: cur.line,
|
|
23
|
+
ch: token.start,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if ((token.type === "punc" || token.type === "error") && !token.state.possibleFullIri && !token.state.inPrefixDecl) {
|
|
27
|
+
token.state.possibleCurrent = token.state.possibleNext;
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
if (prevToken.type === "punc" && !prevToken.state.possibleFullIri && !prevToken.state.inPrefixDecl) {
|
|
31
|
+
//assuming this is a path expression. Should not expand the token anymore
|
|
32
|
+
//Also checking whether current token isnt an error, to avoid stopping on iri path delimiters
|
|
33
|
+
return token;
|
|
34
|
+
}
|
|
35
|
+
// not start of line, and not whitespace
|
|
36
|
+
if (prevToken.type != null && prevToken.type != "ws" && token.type != null && token.type != "ws") {
|
|
37
|
+
token.start = prevToken.start;
|
|
38
|
+
token.string = prevToken.string + token.string;
|
|
39
|
+
return expandTokenToStart(yasqe, token, {
|
|
40
|
+
line: cur.line,
|
|
41
|
+
ch: prevToken.start,
|
|
42
|
+
}); // recursively, might have multiple tokens which it should include
|
|
43
|
+
} else if (token.type != null && token.type == "ws") {
|
|
44
|
+
//always keep 1 char of whitespace between tokens. Otherwise, autocompletions might end up next to the previous node, without whitespace between them
|
|
45
|
+
token.start = token.start + 1;
|
|
46
|
+
token.string = token.string.substring(1);
|
|
47
|
+
return token;
|
|
48
|
+
} else {
|
|
49
|
+
return token;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function expandTokenToEnd(yasqe: Yasqe, token: Token, cur: Position): Token {
|
|
54
|
+
if (token.string.indexOf(" ") >= 0) {
|
|
55
|
+
/**
|
|
56
|
+
* This is most likely a query ending with `<http://www.opengis.net/ont/geosparql# ?a`
|
|
57
|
+
* ^ cursor
|
|
58
|
+
* In this case, separate by whitespace, and assume we're finished
|
|
59
|
+
*/
|
|
60
|
+
const whitespaceIndex = token.string.indexOf(" ");
|
|
61
|
+
token.string = token.string.substr(0, whitespaceIndex);
|
|
62
|
+
token.end = token.start + token.string.length;
|
|
63
|
+
return token;
|
|
64
|
+
}
|
|
65
|
+
if (!token.type) return token;
|
|
66
|
+
|
|
67
|
+
var nextToken: Token = yasqe.getTokenAt({
|
|
68
|
+
line: cur.line,
|
|
69
|
+
ch: token.end + 1,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
// not end of line
|
|
74
|
+
nextToken.type !== "ws" &&
|
|
75
|
+
//not a punctuation (for eg '?subject}', we dont want to include '}')
|
|
76
|
+
token.state.possibleFullIri &&
|
|
77
|
+
//not whitespace
|
|
78
|
+
token.type !== null &&
|
|
79
|
+
token.type !== "ws" &&
|
|
80
|
+
// Avoid infinite loops as CM will give back the last token of in a line when requesting something larger then the lines length
|
|
81
|
+
nextToken.end !== token.end
|
|
82
|
+
) {
|
|
83
|
+
token.end = nextToken.end;
|
|
84
|
+
token.string = token.string + nextToken.string;
|
|
85
|
+
return expandTokenToEnd(yasqe, token, {
|
|
86
|
+
line: cur.line,
|
|
87
|
+
ch: nextToken.end,
|
|
88
|
+
}); // recursively, might have multiple tokens which it should include
|
|
89
|
+
} else if (token.type === "ws") {
|
|
90
|
+
//always keep 1 char of whitespace between tokens. Otherwise, autocompletions might end up next to the previous node, without whitespace between them
|
|
91
|
+
token.end = token.end + 1;
|
|
92
|
+
token.string = token.string.substring(token.string.length - 1);
|
|
93
|
+
return token;
|
|
94
|
+
} else {
|
|
95
|
+
return token;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function getPreviousNonWsToken(yasqe: Yasqe, line: number, token: Token): Token {
|
|
99
|
+
var previousToken = yasqe.getTokenAt({
|
|
100
|
+
line: line,
|
|
101
|
+
ch: token.start,
|
|
102
|
+
});
|
|
103
|
+
if (previousToken != null && previousToken.type == "ws") {
|
|
104
|
+
previousToken = getPreviousNonWsToken(yasqe, line, previousToken);
|
|
105
|
+
}
|
|
106
|
+
return previousToken;
|
|
107
|
+
}
|
|
108
|
+
export function getNextNonWsToken(yasqe: Yasqe, lineNumber: number, charNumber?: number): Token | undefined {
|
|
109
|
+
if (charNumber == undefined) charNumber = 1;
|
|
110
|
+
var token = yasqe.getTokenAt({
|
|
111
|
+
line: lineNumber,
|
|
112
|
+
ch: charNumber,
|
|
113
|
+
});
|
|
114
|
+
if (token == null || token == undefined || token.end < charNumber) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (token.type == "ws") {
|
|
118
|
+
return getNextNonWsToken(yasqe, lineNumber, token.end + 1);
|
|
119
|
+
}
|
|
120
|
+
return token;
|
|
121
|
+
}
|
package/src/tooltip.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write our own tooltip, to avoid loading another library for just this functionality. For now, we only use tooltip for showing parse errors, so this is quite a tailored solution
|
|
3
|
+
* Requirements:
|
|
4
|
+
* position tooltip within codemirror frame as much as possible, to avoid z-index issues with external things on page
|
|
5
|
+
* use html as content
|
|
6
|
+
*/
|
|
7
|
+
import Yasqe from "./";
|
|
8
|
+
|
|
9
|
+
export default function tooltip(_yasqe: Yasqe, parent: HTMLDivElement, html: string) {
|
|
10
|
+
var tooltip: HTMLDivElement;
|
|
11
|
+
parent.onmouseover = function () {
|
|
12
|
+
if (!tooltip) {
|
|
13
|
+
tooltip = document.createElement("div");
|
|
14
|
+
tooltip.className = "yasqe_tooltip";
|
|
15
|
+
}
|
|
16
|
+
// if ($(yasqe.getWrapperElement()).offset().top >= tooltip.offset().top) {
|
|
17
|
+
//shit, move the tooltip down. The tooltip now hovers over the top edge of the yasqe instance
|
|
18
|
+
// tooltip.css("bottom", "auto");
|
|
19
|
+
// tooltip.css("top", "26px");
|
|
20
|
+
// }
|
|
21
|
+
tooltip.style.display = "block";
|
|
22
|
+
tooltip.innerHTML = html;
|
|
23
|
+
parent.appendChild(tooltip);
|
|
24
|
+
};
|
|
25
|
+
parent.onmouseout = function () {
|
|
26
|
+
if (tooltip) {
|
|
27
|
+
tooltip.style.display = "none";
|
|
28
|
+
}
|
|
29
|
+
tooltip.innerHTML = html;
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/trie.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
export default class Trie {
|
|
2
|
+
private words = 0;
|
|
3
|
+
private prefixes = 0;
|
|
4
|
+
private children: { [char: string]: Trie } = {};
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Insert a word into the dictionary.
|
|
8
|
+
* Recursively traverse through the trie nodes, and create new node if does not already exist.
|
|
9
|
+
*
|
|
10
|
+
* @method insert
|
|
11
|
+
* @param {String} str Word to insert in the dictionary
|
|
12
|
+
* @param {Integer} pos Current index of the string to be inserted
|
|
13
|
+
* @return {Void}
|
|
14
|
+
*/
|
|
15
|
+
insert(str: string, pos?: number) {
|
|
16
|
+
if (str.length == 0) {
|
|
17
|
+
//blank string cannot be inserted
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var T = this,
|
|
22
|
+
k,
|
|
23
|
+
child;
|
|
24
|
+
|
|
25
|
+
if (pos === undefined) {
|
|
26
|
+
pos = 0;
|
|
27
|
+
}
|
|
28
|
+
if (pos === str.length) {
|
|
29
|
+
T.words++;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
T.prefixes++;
|
|
33
|
+
k = str[pos];
|
|
34
|
+
if (T.children[k] === undefined) {
|
|
35
|
+
//if node for this char doesn't exist, create one
|
|
36
|
+
T.children[k] = new Trie();
|
|
37
|
+
}
|
|
38
|
+
child = T.children[k];
|
|
39
|
+
child.insert(str, pos + 1);
|
|
40
|
+
}
|
|
41
|
+
/*
|
|
42
|
+
* Remove a word from the dictionary.
|
|
43
|
+
*
|
|
44
|
+
* @method remove
|
|
45
|
+
* @param {String} str Word to be removed
|
|
46
|
+
* @param {Integer} pos Current index of the string to be removed
|
|
47
|
+
* @return {Void}
|
|
48
|
+
*/
|
|
49
|
+
remove(str: string, pos?: number) {
|
|
50
|
+
if (str.length == 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
var T = this,
|
|
54
|
+
k,
|
|
55
|
+
child;
|
|
56
|
+
|
|
57
|
+
if (pos === undefined) {
|
|
58
|
+
pos = 0;
|
|
59
|
+
}
|
|
60
|
+
if (T === undefined) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (pos === str.length) {
|
|
64
|
+
T.words--;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
T.prefixes--;
|
|
68
|
+
k = str[pos];
|
|
69
|
+
child = T.children[k];
|
|
70
|
+
child.remove(str, pos + 1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Update an existing word in the dictionary.
|
|
75
|
+
* This method removes the old word from the dictionary and inserts the new word.
|
|
76
|
+
*
|
|
77
|
+
* @method update
|
|
78
|
+
* @param {String} strOld The old word to be replaced
|
|
79
|
+
* @param {String} strNew The new word to be inserted
|
|
80
|
+
* @return {Void}
|
|
81
|
+
*/
|
|
82
|
+
update(strOld: string, strNew: string) {
|
|
83
|
+
if (strOld.length == 0 || strNew.length == 0) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.remove(strOld);
|
|
87
|
+
this.insert(strNew);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* Count the number of times a given word has been inserted into the dictionary
|
|
92
|
+
*
|
|
93
|
+
* @method countWord
|
|
94
|
+
* @param {String} str Word to get count of
|
|
95
|
+
* @param {Integer} pos Current index of the given word
|
|
96
|
+
* @return {Integer} The number of times a given word exists in the dictionary
|
|
97
|
+
*/
|
|
98
|
+
countWord(str: string, pos?: number) {
|
|
99
|
+
if (str.length == 0) {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
var T = this,
|
|
104
|
+
k,
|
|
105
|
+
child,
|
|
106
|
+
ret = 0;
|
|
107
|
+
|
|
108
|
+
if (pos === undefined) {
|
|
109
|
+
pos = 0;
|
|
110
|
+
}
|
|
111
|
+
if (pos === str.length) {
|
|
112
|
+
return T.words;
|
|
113
|
+
}
|
|
114
|
+
k = str[pos];
|
|
115
|
+
child = T.children[k];
|
|
116
|
+
if (child !== undefined) {
|
|
117
|
+
//node exists
|
|
118
|
+
ret = child.countWord(str, pos + 1);
|
|
119
|
+
}
|
|
120
|
+
return ret;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
* Count the number of times a given prefix exists in the dictionary
|
|
125
|
+
*
|
|
126
|
+
* @method countPrefix
|
|
127
|
+
* @param {String} str Prefix to get count of
|
|
128
|
+
* @param {Integer} pos Current index of the given prefix
|
|
129
|
+
* @return {Integer} The number of times a given prefix exists in the dictionary
|
|
130
|
+
*/
|
|
131
|
+
countPrefix(str: string, pos: number) {
|
|
132
|
+
if (str.length == 0) {
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var T = this,
|
|
137
|
+
k: string,
|
|
138
|
+
child,
|
|
139
|
+
ret = 0;
|
|
140
|
+
|
|
141
|
+
if (pos === undefined) {
|
|
142
|
+
pos = 0;
|
|
143
|
+
}
|
|
144
|
+
if (pos === str.length) {
|
|
145
|
+
return T.prefixes;
|
|
146
|
+
}
|
|
147
|
+
var k = str[pos];
|
|
148
|
+
child = T.children[k];
|
|
149
|
+
if (child !== undefined) {
|
|
150
|
+
//node exists
|
|
151
|
+
ret = child.countPrefix(str, pos + 1);
|
|
152
|
+
}
|
|
153
|
+
return ret;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* Find a word in the dictionary
|
|
158
|
+
*
|
|
159
|
+
* @method find
|
|
160
|
+
* @param {String} str The word to find in the dictionary
|
|
161
|
+
* @return {Boolean} True if the word exists in the dictionary, else false
|
|
162
|
+
*/
|
|
163
|
+
find(str: string) {
|
|
164
|
+
if (str.length == 0) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (this.countWord(str) > 0) {
|
|
169
|
+
return true;
|
|
170
|
+
} else {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* Get all words in the dictionary
|
|
177
|
+
*
|
|
178
|
+
* @method getAllWords
|
|
179
|
+
* @param {String} str Prefix of current word
|
|
180
|
+
* @return {Array} Array of words in the dictionary
|
|
181
|
+
*/
|
|
182
|
+
getAllWords(str: string): string[] {
|
|
183
|
+
var T = this,
|
|
184
|
+
k,
|
|
185
|
+
child,
|
|
186
|
+
ret = [];
|
|
187
|
+
if (str === undefined) {
|
|
188
|
+
str = "";
|
|
189
|
+
}
|
|
190
|
+
if (T === undefined) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
if (T.words > 0) {
|
|
194
|
+
ret.push(str);
|
|
195
|
+
}
|
|
196
|
+
for (k in T.children) {
|
|
197
|
+
if (T.children.hasOwnProperty(k)) {
|
|
198
|
+
child = T.children[k];
|
|
199
|
+
ret = ret.concat(child.getAllWords(str + k));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return ret;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/*
|
|
206
|
+
* Autocomplete a given prefix
|
|
207
|
+
*
|
|
208
|
+
* @method autoComplete
|
|
209
|
+
* @param {String} str Prefix to be completed based on dictionary entries
|
|
210
|
+
* @param {Integer} pos Current index of the prefix
|
|
211
|
+
* @return {Array} Array of possible suggestions
|
|
212
|
+
*/
|
|
213
|
+
autoComplete(str: string, pos?: number): string[] {
|
|
214
|
+
var T = this,
|
|
215
|
+
k,
|
|
216
|
+
child;
|
|
217
|
+
if (str.length == 0) {
|
|
218
|
+
if (pos === undefined) {
|
|
219
|
+
return T.getAllWords(str);
|
|
220
|
+
} else {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (pos === undefined) {
|
|
225
|
+
pos = 0;
|
|
226
|
+
}
|
|
227
|
+
k = str[pos];
|
|
228
|
+
child = T.children[k];
|
|
229
|
+
if (child === undefined) {
|
|
230
|
+
//node doesn't exist
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
if (pos === str.length - 1) {
|
|
234
|
+
return child.getAllWords(str);
|
|
235
|
+
}
|
|
236
|
+
return child.autoComplete(str, pos + 1);
|
|
237
|
+
}
|
|
238
|
+
}
|