101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
import type { JsonValue } from '../domain/canonical-model';
|
|
import type { DecodedObjectView, DecodedSourceDocument } from '../domain/import-intermediate';
|
|
import type { FindingCandidate, SourceExtractor, SourceFieldCandidate } from '../domain/source-extraction';
|
|
import { stringifyCell } from '../shared/text';
|
|
import { cisCatSourceExtractor } from './cis-cat-source-extractor';
|
|
import { pingCastleSourceExtractor } from './pingcastle-source-extractor';
|
|
import { maesterHtmlSourceExtractor } from './maester-html-source-extractor';
|
|
import { purpleKnightHtmlSourceExtractor } from './purple-knight-html-source-extractor';
|
|
|
|
function safeId(value: string): string {
|
|
return value.replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
}
|
|
|
|
function objectFields(
|
|
fileName: string,
|
|
value: JsonValue,
|
|
objectPath: string
|
|
): SourceFieldCandidate[] {
|
|
const entries = value && typeof value === 'object' && !Array.isArray(value)
|
|
? Object.entries(value)
|
|
: [['value', value] as const];
|
|
return entries.map(([key, item]) => ({
|
|
key,
|
|
label: key,
|
|
originalValue: item,
|
|
decodedValue: stringifyCell(item),
|
|
source: { fileName, objectPath: `${objectPath}.${key}` }
|
|
}));
|
|
}
|
|
|
|
function objectCandidates(document: DecodedSourceDocument, view: DecodedObjectView): FindingCandidate[] {
|
|
const values = Array.isArray(view.value) ? view.value : [view.value];
|
|
return values.map((value, index) => {
|
|
const objectPath = Array.isArray(view.value) ? `$[${index}]` : '$';
|
|
return {
|
|
version: 1,
|
|
id: `candidate-${safeId(view.id)}-${index}`,
|
|
source: { fileName: document.file.name, objectPath },
|
|
fields: objectFields(document.file.name, value, objectPath)
|
|
};
|
|
});
|
|
}
|
|
|
|
export const nativeObjectSourceExtractor: SourceExtractor = {
|
|
id: 'native-object-source-extractor',
|
|
version: '1.0.0',
|
|
detect(document) {
|
|
const count = document.views.filter(view => view.kind === 'object').length;
|
|
return count
|
|
? { score: 1, evidence: [`${count} native Objekt- oder Arrayansicht(en) vorhanden.`] }
|
|
: { score: 0, evidence: [] };
|
|
},
|
|
extract(document) {
|
|
const candidates = document.views
|
|
.filter((view): view is DecodedObjectView => view.kind === 'object')
|
|
.flatMap(view => objectCandidates(document, view));
|
|
return { version: 1, candidates, diagnostics: [] };
|
|
}
|
|
};
|
|
|
|
export const genericTableSourceExtractor: SourceExtractor = {
|
|
id: 'generic-table-source-extractor',
|
|
version: '1.0.0',
|
|
fallback: true,
|
|
detect(document) {
|
|
const count = document.tables.length;
|
|
return count
|
|
? { score: 0.1, evidence: [`${count} kompatible Tabellenansicht(en) vorhanden.`] }
|
|
: { score: 0, evidence: [] };
|
|
},
|
|
extract(document) {
|
|
const candidates = document.tables.flatMap(table => {
|
|
const header = table.headerCandidates[0];
|
|
return table.rows
|
|
.filter(row => row.index !== header?.rowIndex && row.cells.some(cell => cell.decodedValue.trim()))
|
|
.map(row => ({
|
|
version: 1 as const,
|
|
id: `candidate-${safeId(table.id)}-${row.index}`,
|
|
source: { fileName: document.file.name, tableIndex: table.index, rowIndex: row.index },
|
|
fields: row.cells.map(cell => ({
|
|
key: `column-${cell.source.columnIndex ?? 0}`,
|
|
label: header?.headers[cell.source.columnIndex ?? 0] || `Spalte ${(cell.source.columnIndex ?? 0) + 1}`,
|
|
originalValue: cell.rawValue,
|
|
decodedValue: cell.decodedValue,
|
|
source: { ...cell.source, fileName: document.file.name }
|
|
}))
|
|
}));
|
|
});
|
|
return { version: 1, candidates, diagnostics: [] };
|
|
}
|
|
};
|
|
|
|
export const sourceExtractors: SourceExtractor[] = [
|
|
pingCastleSourceExtractor,
|
|
cisCatSourceExtractor,
|
|
purpleKnightHtmlSourceExtractor,
|
|
maesterHtmlSourceExtractor,
|
|
nativeObjectSourceExtractor,
|
|
genericTableSourceExtractor
|
|
];
|