Data extraction API improvements.

This commit is contained in:
Henning Dieterichs
2023-03-24 14:06:22 +01:00
parent 904d0060ea
commit d45dfdaa71
5 changed files with 220 additions and 32 deletions

View File

@@ -53,6 +53,11 @@ export interface DataExtractor {
* Must be unique among all data extractors.
*/
id: string;
/**
* Filters the data to be extracted.
*/
dataCtor?: string;
getExtractions(
data: unknown,
extractionCollector: ExtractionCollector,
@@ -73,7 +78,11 @@ export interface DataExtractorContext {
*/
evalFn: <TEval>(expression: string) => TEval;
expression: string | undefined;
variablesInScope: Record<string, () => unknown>;
extract(value: unknown): VisualizationData | undefined;
}
export interface DataExtraction {
@@ -86,10 +95,12 @@ export interface DataExtraction {
* A unique id identifying this extraction among all extractions.
* Required to express extraction preferences.
*/
id: string;
id?: string;
/**
* A user friendly name of this extraction.
*/
name: string;
name?: string;
extractData(): VisualizationData;
}

View File

@@ -7,7 +7,10 @@ import {
ExtractionCollector,
DataExtractorContext,
} from "./DataExtractorApi";
import { DataExtractorInfo } from "../../DataExtractionResult";
import {
DataExtractorInfo,
VisualizationData,
} from "../../DataExtractionResult";
import { registerDefaultExtractors } from "./default-extractors";
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
import * as helpers from "../helpers";
@@ -41,36 +44,56 @@ export class DataExtractorApiImpl implements DataExtractorApi {
preferredDataExtractorId: string | undefined,
variablesInScope: Record<string, () => unknown>
): JSONString<DataResult> {
const extractions = new Array<DataExtraction>();
const extractionCollector: ExtractionCollector = {
addExtraction(extraction) {
extractions.push(extraction);
},
};
class ContextImpl implements DataExtractorContext {
constructor(
public readonly variablesInScope: Record<string, () => unknown>,
public readonly expression: string | undefined,
public readonly evalFn: <T>(expression: string) => T,
private readonly _api: DataExtractorApiImpl,
private readonly _parent: ContextImpl | undefined
) {}
const context: DataExtractorContext = {
evalFn,
get _level(): number {
return this._parent ? this._parent._level + 1 : 0;
}
extract(value: any): VisualizationData | undefined {
if (this._level > 10) {
throw new Error(
"extract() called too many times recursively"
);
}
const extractions = this._api.getExtractions(
value,
new ContextImpl(
this.variablesInScope,
undefined,
this.evalFn,
this._api,
this
)
);
if (extractions.length === 0) {
return undefined;
}
return extractions[0].extractData();
}
}
const rootContext = new ContextImpl(
variablesInScope,
};
removeEnd(removeStart(valueFn.toString(), "() => ("), ")").trim(),
evalFn,
this,
undefined
);
DataExtractorApiImpl.lastContext = context;
DataExtractorApiImpl.lastContext = rootContext;
const value = valueFn();
const extractors = new Array<DataExtractor>();
for (const fn of this.extractorSources.values()) {
fn((extractor) => {
extractors.push(extractor);
}, helpers);
}
for (const e of [...this.extractors.values(), ...extractors]) {
e.getExtractions(value, extractionCollector, context);
}
const extractions = this.getExtractions(value, rootContext);
DataExtractorApiImpl.lastContext = undefined;
extractions.sort((a, b) => b.priority - a.priority);
let usedExtraction = extractions[0];
if (!usedExtraction) {
return this.toJson({ kind: "NoExtractors" } as DataResult);
@@ -87,8 +110,8 @@ export class DataExtractorApiImpl implements DataExtractorApi {
function mapExtractor(e: DataExtraction): DataExtractorInfo {
return {
id: e.id as any,
name: e.name,
id: e.id! as any,
name: e.name!,
priority: e.priority,
};
}
@@ -104,6 +127,50 @@ export class DataExtractorApiImpl implements DataExtractorApi {
} as DataResult);
}
public getExtractions(
value: unknown,
context: DataExtractorContext
): DataExtraction[] {
const extractions = new Array<DataExtraction>();
const extractors = new Array<DataExtractor>();
for (const fn of this.extractorSources.values()) {
fn((extractor) => {
extractors.push(extractor);
}, helpers);
}
for (const e of [...this.extractors.values(), ...extractors]) {
if (e.dataCtor !== undefined) {
if (
typeof value !== "object" ||
value === null ||
value.constructor.name !== e.dataCtor
) {
continue;
}
}
e.getExtractions(
value,
{
addExtraction(extraction) {
if (extraction.id === undefined) {
extraction.id = e.id;
}
if (extraction.name === undefined) {
extraction.name = e.id;
}
extractions.push(extraction);
},
},
context
);
}
extractions.sort((a, b) => b.priority - a.priority);
return extractions;
}
public registerDefaultExtractors(preferExisting: boolean = false): void {
// TODO consider preferExisting
registerDefaultExtractors(this);
@@ -120,3 +187,17 @@ export class DataExtractorApiImpl implements DataExtractorApi {
}
}
}
function removeStart(str: string, start: string): string {
if (str.startsWith(start)) {
return str.substr(start.length);
}
return str;
}
function removeEnd(str: string, end: string): string {
if (str.endsWith(end)) {
return str.substr(0, str.length - end.length);
}
return str;
}

View File

@@ -50,7 +50,11 @@ export class StringRangeExtractor implements DataExtractor {
for (let item of (data as (number | [number, number])[]).slice(1)) {
if (typeof item === "string") {
item = context.evalFn(item);
try {
item = context.evalFn(item);
} catch (e) {
return;
}
if (item === undefined) {
continue;
}