@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/build/ts/grammar/tokenizer.d.ts +37 -0
  3. package/build/ts/src/CodeMirror.d.ts +21 -0
  4. package/build/ts/src/autocompleters/classes.d.ts +3 -0
  5. package/build/ts/src/autocompleters/index.d.ts +39 -0
  6. package/build/ts/src/autocompleters/prefixes.d.ts +3 -0
  7. package/build/ts/src/autocompleters/properties.d.ts +3 -0
  8. package/build/ts/src/autocompleters/variables.d.ts +3 -0
  9. package/build/ts/src/defaults.d.ts +75 -0
  10. package/build/ts/src/imgs.d.ts +7 -0
  11. package/build/ts/src/index.d.ts +303 -0
  12. package/build/ts/src/prefixFold.d.ts +8 -0
  13. package/build/ts/src/prefixUtils.d.ts +9 -0
  14. package/build/ts/src/sparql.d.ts +20 -0
  15. package/build/ts/src/tokenUtils.d.ts +4 -0
  16. package/build/ts/src/tooltip.d.ts +2 -0
  17. package/build/ts/src/trie.d.ts +13 -0
  18. package/build/yasqe.html +108 -0
  19. package/build/yasqe.min.css +2 -0
  20. package/build/yasqe.min.css.map +1 -0
  21. package/build/yasqe.min.js +3 -0
  22. package/build/yasqe.min.js.LICENSE.txt +3 -0
  23. package/build/yasqe.min.js.map +1 -0
  24. package/grammar/README.md +12 -0
  25. package/grammar/_tokenizer-table.js +4776 -0
  26. package/grammar/build.sh +2 -0
  27. package/grammar/sparql11-grammar.pl +834 -0
  28. package/grammar/sparqljs-browser-min.js +4535 -0
  29. package/grammar/tokenizer.ts +729 -0
  30. package/grammar/util/gen_ll1.pl +37 -0
  31. package/grammar/util/gen_sparql11.pl +11 -0
  32. package/grammar/util/ll1.pl +175 -0
  33. package/grammar/util/output_to_javascript.pl +75 -0
  34. package/grammar/util/prune.pl +49 -0
  35. package/grammar/util/rewrite.pl +104 -0
  36. package/package.json +40 -0
  37. package/src/CodeMirror.ts +54 -0
  38. package/src/autocompleters/classes.ts +32 -0
  39. package/src/autocompleters/index.ts +346 -0
  40. package/src/autocompleters/prefixes.ts +130 -0
  41. package/src/autocompleters/properties.ts +28 -0
  42. package/src/autocompleters/show-hint.scss +38 -0
  43. package/src/autocompleters/variables.ts +52 -0
  44. package/src/defaults.ts +149 -0
  45. package/src/imgs.ts +14 -0
  46. package/src/index.ts +1089 -0
  47. package/src/prefixFold.ts +93 -0
  48. package/src/prefixUtils.ts +65 -0
  49. package/src/scss/buttons.scss +275 -0
  50. package/src/scss/codemirrorMods.scss +36 -0
  51. package/src/scss/yasqe.scss +89 -0
  52. package/src/sparql.ts +215 -0
  53. package/src/tokenUtils.ts +121 -0
  54. package/src/tooltip.ts +31 -0
  55. 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
+ }