diff --git a/lib/ttcQuery/TTCQueryParser.ts b/lib/ttcQuery/TTCQueryParser.ts new file mode 100644 index 0000000..dd38aaa --- /dev/null +++ b/lib/ttcQuery/TTCQueryParser.ts @@ -0,0 +1,177 @@ +type QueryableObject = Record; + +class TtcQueryParser { + private data: QueryableObject; + private relativeMap: Map; + + constructor(data: QueryableObject) { + this.data = data; + this.relativeMap = new Map(); + this.buildRelativeMap(this.data, null); + } + + public search( + query: string, + currentObject: QueryableObject = this.data + ): any[] { + // Normalize the query string by trimming whitespace + query = query.trim(); + + // Determine the base structure to search + let result: any[] = []; + + // Perform initial parsing based on the query syntax + if (query.startsWith("^")) { + result = this.queryCurrentObject(query, currentObject); + } else if (query.startsWith("$")) { + result = this.queryRelativeObject(query, currentObject); + } else { + result = this.queryPublication(query); + } + + return result; + } + + private queryPublication(query: string): any[] { + // Example implementation for searching publication + const publicationMatch = query.match(/^(\w+)(\[(\w+)\])?(\..+)?/); + if (publicationMatch) { + const subQuery = publicationMatch[4]; + + // Search within the publication data + return this.searchInObject(this.data, subQuery); + } + return []; + } + + private queryCurrentObject( + query: string, + currentObject: QueryableObject + ): any[] { + // Example implementation for querying the current object + const subQuery = query.substring(1); // Remove '^' + return this.searchInObject(currentObject, subQuery); + } + + private queryRelativeObject( + query: string, + currentObject: QueryableObject + ): any[] { + const relativeObject = this.relativeMap.get(currentObject); + + if (!relativeObject) { + throw new Error("Relative object not found."); + } + const subQuery = query.substring(1); // Remove '$' + return this.searchInObject(relativeObject, subQuery); + } + + private buildRelativeMap( + obj: QueryableObject, + relative: QueryableObject | null + ): void { + if (obj.relative) { + relative = obj; + } + this.relativeMap.set(obj, relative || obj); + + for (const key in obj) { + if (typeof obj[key] === "object" && obj[key] !== null) { + this.buildRelativeMap(obj[key], relative); + } + } + } + + private searchInObject(obj: any, subQuery?: string): any[] { + // Handle subqueries and search in the provided object + if (!subQuery) { + return [obj]; // Return the entire object if no subquery is present + } + + // Split the subquery based on dot (.) to navigate through the object + const keys = subQuery.split("."); + let current: any = obj; + + for (const key of keys) { + if (current && typeof current === "object") { + if (key.includes("[") && key.includes("]")) { + const [prop, selector] = key.split("["); + const index = selector.slice(0, -1); + + if (Array.isArray(current[prop])) { + if (!isNaN(Number(index))) { + current = current[prop][Number(index)]; + } else { + const [k, comparator, value] = this.parseSelector(index); + current = this.applySelector(current[prop], k, comparator, value); + } + } else { + return []; + } + } else { + current = current.map((e: any) => e[key]); + } + } else { + return []; + } + } + + return Array.isArray(current) ? current : [current]; + } + + private parseSelector(selector: string): [string, string, string] { + const match = selector.match(/(.+)(=|==|>|<|>=|<=|!!|!|!=)(.+)/); + if (match) { + return [match[1], match[2], match[3]]; + } + return ["", "=", selector]; + } + + private applySelector( + array: any[], + key: string, + comparator: string, + value: string + ): any[] { + switch (comparator) { + case "=": + return array.filter((item) => (key ? item[key] : item).includes(value)); + case "==": + return array.filter((item) => (key ? item[key] : item) === value); + case ">": + return array.filter((item) => (key ? item[key] : item) > value); + case "<": + return array.filter((item) => (key ? item[key] : item) < value); + case ">=": + return array.filter((item) => (key ? item[key] : item) >= value); + case "<=": + return array.filter((item) => (key ? item[key] : item) <= value); + case "!!": + return array.filter((item) => (key ? item[key] : item)); + case "!=": + return array.filter( + (item) => !(key ? item[key] : item).includes(value) + ); + case "!": + return array.filter((item) => !(key ? item[key] : item)); + default: + return []; + } + } +} + +// Example usage: +const data = { + relative: true, + weapon_abilities: [ + { name: "Rapid Fire", body: "Shoot again" }, + { name: "Sustained Hits", body: "More damage", relative: true }, + ], +}; + +const parser = new TtcQueryParser(data); + +// Example queries +console.log(parser.search("weapon_abilities[name=Rapid Fire].body")); // Example output +// console.log(parser.search("^weapon_abilities[name=Rapid Fire]", data)); // Example output +console.log(parser.search("$weapon_abilities[name=Rapid Fire].body", data)); // Example output