@opencraw/cli 0.1.17 → 0.1.19
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/dist/index.esm.js +4 -531
- package/dist/src/probe/index.d.ts +2 -14
- package/dist/src/probe/probe-page.use-case.d.ts +2 -7
- package/dist/src/probe/probe-report.mapper.d.ts +1 -7
- package/package.json +4 -3
- package/dist/src/probe/deck-findings.mapper.d.ts +0 -46
- package/dist/src/probe/find-data.algorithm.d.ts +0 -36
- package/dist/src/probe/html-findings.mapper.d.ts +0 -28
- package/dist/src/probe/json-findings.mapper.d.ts +0 -31
- package/dist/src/probe/pdf-findings.mapper.d.ts +0 -26
- package/dist/src/probe/table-header.policy.d.ts +0 -27
- package/dist/src/probe/workbook-findings.mapper.d.ts +0 -44
- package/dist/src/probe/xml-findings.mapper.d.ts +0 -30
package/dist/index.esm.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
2
|
import { writeFile } from 'node:fs/promises';
|
|
3
|
-
import { readableKey, readRecordsFile, diffRecords, loadAccessConfig,
|
|
3
|
+
import { readableKey, readRecordsFile, diffRecords, loadAccessConfig, HostAllowlist, AccessBroker, HttpClient, parseXml, BrowserClient, readRecipeSource, parseOutputRecipe, parseInputRecipe, bindRecipeSet, memorySink, jsonLinesSink, createCrawler, RecipeSet, variantLabel, diffOptionsFor, RecipeValidationError, RecipeBindingError, traceLine } from '@opencraw/core';
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { describePdf, findData, describeWorkbook, describeDeck, describeJson, describeXml, describeHtml } from '@opencraw/probe';
|
|
7
|
+
export { findData } from '@opencraw/probe';
|
|
6
8
|
import { readFileSync } from 'node:fs';
|
|
7
9
|
|
|
8
10
|
const USAGE = `opencraw <command> [options]
|
|
@@ -457,455 +459,6 @@ function hookExports(exported) {
|
|
|
457
459
|
return Object.fromEntries(named);
|
|
458
460
|
}
|
|
459
461
|
|
|
460
|
-
/**
|
|
461
|
-
* Finds where a fetched page's data lives: JSON-LD blocks, inline JSON objects
|
|
462
|
-
* big enough to matter, URLs that look like a data endpoint, script hosts and
|
|
463
|
-
* links whose path suggests an API. Pattern-matching only; it never fetches.
|
|
464
|
-
*/
|
|
465
|
-
const MIN_INLINE_SIZE = 200;
|
|
466
|
-
const JSON_LD = /<script[^\s>]*\s+type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
|
|
467
|
-
const INLINE_SCRIPT = /<script(?![^>]*\ssrc=)(?![^>]*\stype=["']application\/ld\+json["'])[^>]*>([\s\S]*?)<\/script>/gi;
|
|
468
|
-
const DATA_ATTR = /data-[\w-]+=["'](\{[\s\S]*?\})["']/g;
|
|
469
|
-
const SCRIPT_SRC = /<script[^\s>]*\s+src=["']([^"']+)["']/gi;
|
|
470
|
-
const HREF_OR_JSON_URL = /["'](https?:\/\/[^"'\s]+\.json[^"'\s]*|\/[^"'\s]*\.json[^"'\s]*)["']/g;
|
|
471
|
-
const LINK = /<a[^\s>]*\s+href=["']([^"']+)["']/gi;
|
|
472
|
-
/**
|
|
473
|
-
* Scans a page's HTML for data-shaped things.
|
|
474
|
-
*
|
|
475
|
-
* @param html - The document.
|
|
476
|
-
* @returns What was found, each list capped at a handful of entries.
|
|
477
|
-
*/
|
|
478
|
-
function findData(html) {
|
|
479
|
-
return {
|
|
480
|
-
jsonLd: jsonLdBlocks(html),
|
|
481
|
-
inlineJson: inlineJsonBlocks(html),
|
|
482
|
-
jsonUrls: unique(matchesOf(HREF_OR_JSON_URL, html)).slice(0, 10),
|
|
483
|
-
scriptHosts: scriptHosts(html),
|
|
484
|
-
apiLinks: apiLinks(html)
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
function jsonLdBlocks(html) {
|
|
488
|
-
const findings = [];
|
|
489
|
-
for (const text of matchesOf(JSON_LD, html)) {
|
|
490
|
-
const parsed = tryParse(text);
|
|
491
|
-
if (parsed === undefined) continue;
|
|
492
|
-
findings.push(...entriesOf(parsed));
|
|
493
|
-
}
|
|
494
|
-
return findings.slice(0, 10);
|
|
495
|
-
}
|
|
496
|
-
/** Every plain object in a JSON-LD block (it may be one object or an array of them), as a finding. */
|
|
497
|
-
function entriesOf(parsed) {
|
|
498
|
-
const list = Array.isArray(parsed) ? parsed : [parsed];
|
|
499
|
-
return list.flatMap(entry => {
|
|
500
|
-
if (typeof entry !== 'object' || entry === null) return [];
|
|
501
|
-
const record = entry;
|
|
502
|
-
const type = record['@type'];
|
|
503
|
-
return [{
|
|
504
|
-
types: Array.isArray(type) ? type.join(',') : String(type ?? '?'),
|
|
505
|
-
keys: Object.keys(record).length
|
|
506
|
-
}];
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
function inlineJsonBlocks(html) {
|
|
510
|
-
const findings = [];
|
|
511
|
-
for (const text of matchesOf(INLINE_SCRIPT, html)) findings.push(...objectsIn(text, 'inline script'));
|
|
512
|
-
for (const text of matchesOf(DATA_ATTR, html, 1)) findings.push(...objectsIn(text, 'data-* attribute'));
|
|
513
|
-
return findings.filter(finding => finding.size >= MIN_INLINE_SIZE).sort((a, b) => b.size - a.size).slice(0, 10);
|
|
514
|
-
}
|
|
515
|
-
/** Every `{...}` in a text that parses as JSON, by scanning for balanced braces (regex cannot nest). */
|
|
516
|
-
function objectsIn(text, where) {
|
|
517
|
-
const found = [];
|
|
518
|
-
for (let at = text.indexOf('{'); at !== -1; at = text.indexOf('{', at + 1)) {
|
|
519
|
-
const end = matchingBrace(text, at);
|
|
520
|
-
if (end === -1) continue;
|
|
521
|
-
const candidate = text.slice(at, end + 1);
|
|
522
|
-
const parsed = tryParse(candidate);
|
|
523
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
524
|
-
found.push({
|
|
525
|
-
where,
|
|
526
|
-
keys: Object.keys(parsed),
|
|
527
|
-
size: candidate.length
|
|
528
|
-
});
|
|
529
|
-
at = end;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return found;
|
|
533
|
-
}
|
|
534
|
-
function matchingBrace(text, open) {
|
|
535
|
-
let depth = 0;
|
|
536
|
-
for (let at = open; at < text.length; at += 1) {
|
|
537
|
-
if (text[at] === '{') depth += 1;else if (text[at] === '}') {
|
|
538
|
-
depth -= 1;
|
|
539
|
-
if (depth === 0) return at;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return -1;
|
|
543
|
-
}
|
|
544
|
-
function scriptHosts(html) {
|
|
545
|
-
const hosts = matchesOf(SCRIPT_SRC, html).map(src => {
|
|
546
|
-
try {
|
|
547
|
-
return new URL(src).host;
|
|
548
|
-
} catch {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
}).filter(host => host !== undefined);
|
|
552
|
-
return unique(hosts).slice(0, 10);
|
|
553
|
-
}
|
|
554
|
-
function apiLinks(html) {
|
|
555
|
-
return unique(matchesOf(LINK, html).filter(href => /\bapi\b/i.test(href))).slice(0, 10);
|
|
556
|
-
}
|
|
557
|
-
function matchesOf(pattern, text, group = 1) {
|
|
558
|
-
return Array.from(text.matchAll(pattern), match => match[group]);
|
|
559
|
-
}
|
|
560
|
-
function unique(values) {
|
|
561
|
-
return [...new Set(values)];
|
|
562
|
-
}
|
|
563
|
-
function tryParse(text) {
|
|
564
|
-
try {
|
|
565
|
-
return JSON.parse(text.trim());
|
|
566
|
-
} catch {
|
|
567
|
-
return undefined;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Whether a cell holds a figure (an amount, a count, a date written in
|
|
573
|
-
* digits) rather than words: it starts with a number, or its digits are at
|
|
574
|
-
* least as many as its letters. `2,820`, `$560`, `2659 $560`, `(£0.58m)`,
|
|
575
|
-
* `-3 days` and `2026` are figures; `FY26 Lodging Rate`,
|
|
576
|
-
* `FM Classes A, B1 & C3` and `(APRIL 1, 2020)` are words that happen to
|
|
577
|
-
* contain digits.
|
|
578
|
-
*
|
|
579
|
-
* @param cell - The cell's text.
|
|
580
|
-
* @returns Whether it is a figure.
|
|
581
|
-
*/
|
|
582
|
-
function isFigure(cell) {
|
|
583
|
-
if (/^[\s(+\-–−<>=~$€£¥]*\d/u.test(cell)) return true;
|
|
584
|
-
const digits = cell.match(/\d/g)?.length ?? 0;
|
|
585
|
-
return digits > 0 && digits >= (cell.match(/\p{L}/gu)?.length ?? 0);
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* A row that reads like a table's header: at least two cells, none a figure.
|
|
589
|
-
*
|
|
590
|
-
* @param cells - The row's non-empty cells.
|
|
591
|
-
* @returns Whether it could be a header.
|
|
592
|
-
*/
|
|
593
|
-
function isHeaderRow(cells) {
|
|
594
|
-
return cells.length >= 2 && cells.every(cell => !isFigure(cell));
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* A row that reads like a table's data: at least two cells, one a figure.
|
|
598
|
-
*
|
|
599
|
-
* @param cells - The row's non-empty cells.
|
|
600
|
-
* @returns Whether it looks like data.
|
|
601
|
-
*/
|
|
602
|
-
function isDataRow(cells) {
|
|
603
|
-
return cells.length >= 2 && cells.some(cell => isFigure(cell));
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/** Rows shown per sheet in a probe, at most. */
|
|
607
|
-
const ROW_LIMIT$1 = 20;
|
|
608
|
-
/** How far below a header row a probe looks for the first data row. */
|
|
609
|
-
const LOOKAHEAD = 3;
|
|
610
|
-
/**
|
|
611
|
-
* Summarises a workbook for someone writing a recipe: its sheets, what their
|
|
612
|
-
* rows look like and where the tables start, so a `table` extract's `sheet`,
|
|
613
|
-
* `selector` and columns can be read off the result instead of guessed.
|
|
614
|
-
*
|
|
615
|
-
* @param document - The read workbook.
|
|
616
|
-
* @returns The findings.
|
|
617
|
-
*/
|
|
618
|
-
function describeWorkbook(document) {
|
|
619
|
-
const visible = document.sheets.filter(sheet => sheet.hidden !== true);
|
|
620
|
-
return {
|
|
621
|
-
...(document.csv !== undefined && {
|
|
622
|
-
csv: document.csv
|
|
623
|
-
}),
|
|
624
|
-
sheets: document.sheets.map(sheet => ({
|
|
625
|
-
name: sheet.name,
|
|
626
|
-
hidden: sheet.hidden === true,
|
|
627
|
-
rows: sheet.rows.length,
|
|
628
|
-
columns: Math.max(0, ...sheet.rows.map(row => row.length))
|
|
629
|
-
})),
|
|
630
|
-
rows: visible.flatMap(sheet => filledRows(sheet).slice(0, ROW_LIMIT$1).map(({
|
|
631
|
-
index,
|
|
632
|
-
cells
|
|
633
|
-
}) => ({
|
|
634
|
-
sheet: sheet.name,
|
|
635
|
-
row: index + 1,
|
|
636
|
-
text: cells.join(' | ')
|
|
637
|
-
}))),
|
|
638
|
-
headers: visible.flatMap(sheet => headersOf(sheet))
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
function headersOf(sheet) {
|
|
642
|
-
const rows = filledRows(sheet);
|
|
643
|
-
const merged = mergedRows(sheet);
|
|
644
|
-
const headers = [];
|
|
645
|
-
// The table a row belongs to: the header it follows, until an empty row ends the block.
|
|
646
|
-
let table;
|
|
647
|
-
for (const [position, {
|
|
648
|
-
index,
|
|
649
|
-
cells
|
|
650
|
-
}] of rows.entries()) {
|
|
651
|
-
const previous = rows[position - 1]?.index;
|
|
652
|
-
if (previous === undefined || index > previous + 1) table = undefined;
|
|
653
|
-
const secondHeaderRow = table?.merged === true && index === table.header + 1;
|
|
654
|
-
if (table !== undefined && !secondHeaderRow) continue;
|
|
655
|
-
if (!isHeaderRow(cells) || rows.slice(position + 1, position + 1 + LOOKAHEAD).every(row => !isDataRow(row.cells))) continue;
|
|
656
|
-
const header = {
|
|
657
|
-
sheet: sheet.name,
|
|
658
|
-
row: index + 1,
|
|
659
|
-
text: cells.join(' | '),
|
|
660
|
-
selector: `^${escape$1(cells[0])}`
|
|
661
|
-
};
|
|
662
|
-
headers.push(merged.has(index) ? {
|
|
663
|
-
...header,
|
|
664
|
-
hint: 'merged header cells: try "headerRows": 2'
|
|
665
|
-
} : header);
|
|
666
|
-
table ??= {
|
|
667
|
-
header: index,
|
|
668
|
-
merged: merged.has(index)
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
return headers;
|
|
672
|
-
}
|
|
673
|
-
/** The non-empty rows, as their non-empty cells, whitespace collapsed. */
|
|
674
|
-
function filledRows(sheet) {
|
|
675
|
-
const hidden = new Set(sheet.hiddenRows);
|
|
676
|
-
return sheet.rows.flatMap((row, index) => {
|
|
677
|
-
const cells = row.map(cell => String(cell).replaceAll(/\s+/g, ' ').trim()).filter(cell => cell !== '');
|
|
678
|
-
return cells.length === 0 || hidden.has(index) ? [] : [{
|
|
679
|
-
index,
|
|
680
|
-
cells
|
|
681
|
-
}];
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
/** Rows a merged range starts on. */
|
|
685
|
-
function mergedRows(sheet) {
|
|
686
|
-
const rows = new Set();
|
|
687
|
-
const merges = sheet.merges ?? [];
|
|
688
|
-
for (const reference of merges) {
|
|
689
|
-
const match = /^\$?[A-Z]+\$?(\d+)/i.exec(reference);
|
|
690
|
-
if (match !== null) rows.add(Number(match[1]) - 1);
|
|
691
|
-
}
|
|
692
|
-
return rows;
|
|
693
|
-
}
|
|
694
|
-
function escape$1(text) {
|
|
695
|
-
return text.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/** Short text boxes on one slide from which a probe suggests reading them as a table. */
|
|
699
|
-
const GRID_BOXES = 6;
|
|
700
|
-
const SHORT_TEXT = 40;
|
|
701
|
-
/**
|
|
702
|
-
* Summarises a deck for someone writing a recipe: its slides, where the native
|
|
703
|
-
* tables start, what the charts hold, and which slides lay text boxes out as
|
|
704
|
-
* a table.
|
|
705
|
-
*
|
|
706
|
-
* @param document - The read deck.
|
|
707
|
-
* @returns The findings.
|
|
708
|
-
*/
|
|
709
|
-
function describeDeck(document) {
|
|
710
|
-
return {
|
|
711
|
-
width: document.width,
|
|
712
|
-
height: document.height,
|
|
713
|
-
slides: document.slides.map(slide => ({
|
|
714
|
-
number: slide.number,
|
|
715
|
-
title: slide.title ?? '',
|
|
716
|
-
hidden: slide.hidden,
|
|
717
|
-
shapes: slide.shapes.length,
|
|
718
|
-
tables: slide.tables.length,
|
|
719
|
-
charts: slide.charts.length
|
|
720
|
-
})),
|
|
721
|
-
headers: document.slides.flatMap(slide => describeWorkbook({
|
|
722
|
-
sheets: slide.tables
|
|
723
|
-
}).headers.map(({
|
|
724
|
-
sheet: _sheet,
|
|
725
|
-
row: _row,
|
|
726
|
-
...header
|
|
727
|
-
}) => ({
|
|
728
|
-
slide: slide.number,
|
|
729
|
-
...header
|
|
730
|
-
}))),
|
|
731
|
-
charts: document.slides.flatMap(slide => slide.charts.map(chart => ({
|
|
732
|
-
slide: slide.number,
|
|
733
|
-
type: chart.type,
|
|
734
|
-
...(chart.title !== undefined && {
|
|
735
|
-
title: chart.title
|
|
736
|
-
}),
|
|
737
|
-
series: chart.series.map(series => ({
|
|
738
|
-
name: series.name,
|
|
739
|
-
points: series.values.length
|
|
740
|
-
}))
|
|
741
|
-
}))),
|
|
742
|
-
grids: document.slides.flatMap(slide => {
|
|
743
|
-
const boxes = slide.shapes.filter(shape => shape.placeholder === undefined && shape.text.length <= SHORT_TEXT).length;
|
|
744
|
-
return boxes >= GRID_BOXES ? [{
|
|
745
|
-
slide: slide.number,
|
|
746
|
-
title: slide.title ?? '',
|
|
747
|
-
boxes
|
|
748
|
-
}] : [];
|
|
749
|
-
})
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
const SECTION = /<section data-heading="([^"]*)" data-level="(\d)">/g;
|
|
754
|
-
const FRONT_MATTER = /<script type="application\/json" data-front-matter>([\s\S]*?)<\/script>/;
|
|
755
|
-
/**
|
|
756
|
-
* Summarises an HTML document for a `table` extract, and, when it is rendered
|
|
757
|
-
* Markdown or a Word document, its outline (and Markdown's front matter).
|
|
758
|
-
*
|
|
759
|
-
* @param html - The document.
|
|
760
|
-
* @param markdown - Whether it was rendered from Markdown or Word, and so has sections.
|
|
761
|
-
* @returns The findings.
|
|
762
|
-
*/
|
|
763
|
-
function describeHtml(html, markdown) {
|
|
764
|
-
const tables = htmlTableSheets(html);
|
|
765
|
-
const headers = describeWorkbook({
|
|
766
|
-
sheets: tables
|
|
767
|
-
}).headers.map(({
|
|
768
|
-
sheet,
|
|
769
|
-
row: _row,
|
|
770
|
-
...header
|
|
771
|
-
}) => ({
|
|
772
|
-
table: sheet,
|
|
773
|
-
...header
|
|
774
|
-
}));
|
|
775
|
-
if (!markdown) return {
|
|
776
|
-
tables: headers
|
|
777
|
-
};
|
|
778
|
-
const outline = Array.from(html.matchAll(SECTION), ([, heading, level]) => ({
|
|
779
|
-
level: Number(level),
|
|
780
|
-
heading: unescape(heading),
|
|
781
|
-
selector: `section[data-heading='${unescape(heading).replaceAll("'", String.raw`\'`)}' i]`
|
|
782
|
-
}));
|
|
783
|
-
const front = FRONT_MATTER.exec(html)?.[1];
|
|
784
|
-
const data = front === undefined ? undefined : JSON.parse(front);
|
|
785
|
-
return {
|
|
786
|
-
tables: headers,
|
|
787
|
-
outline,
|
|
788
|
-
frontMatter: typeof data === 'object' && data !== null && !Array.isArray(data) ? Object.keys(data) : []
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
function unescape(text) {
|
|
792
|
-
return text.replaceAll('"', '"').replaceAll('<', '<').replaceAll('&', '&');
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/** How deep a probe describes a JSON document. */
|
|
796
|
-
const DEPTH$1 = 4;
|
|
797
|
-
/** Keys shown per object, at most. */
|
|
798
|
-
const KEY_LIMIT = 25;
|
|
799
|
-
/** Entries of a record list whose keys are compared. */
|
|
800
|
-
const SAMPLE = 50;
|
|
801
|
-
/** A sample value longer than this is cut. */
|
|
802
|
-
const SAMPLE_CHARS$1 = 40;
|
|
803
|
-
/**
|
|
804
|
-
* Summarises JSON data for someone writing a recipe: its shape, and where its
|
|
805
|
-
* record lists are, so a `jsonpath` selector can be read off the result.
|
|
806
|
-
*
|
|
807
|
-
* @param data - The parsed document.
|
|
808
|
-
* @param format - What it was read as (`json`, `jsonl`, `yaml`).
|
|
809
|
-
* @returns The findings.
|
|
810
|
-
*/
|
|
811
|
-
function describeJson(data, format) {
|
|
812
|
-
const tree = [];
|
|
813
|
-
const lists = [];
|
|
814
|
-
walk$1(data, '$', 0, tree, lists);
|
|
815
|
-
return {
|
|
816
|
-
format,
|
|
817
|
-
type: typeOf(data),
|
|
818
|
-
tree,
|
|
819
|
-
lists: lists.sort((first, second) => second.length - first.length)
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
function walk$1(value, path, depth, tree, lists) {
|
|
823
|
-
if (Array.isArray(value)) {
|
|
824
|
-
const objects = value.filter(entry => isObject(entry));
|
|
825
|
-
if (objects.length >= 2 && objects.length === value.length) lists.push({
|
|
826
|
-
path: `${path}[*]`,
|
|
827
|
-
length: value.length,
|
|
828
|
-
keys: sharedKeys(objects.slice(0, SAMPLE))
|
|
829
|
-
});
|
|
830
|
-
if (depth > 0) tree.push(`${path} ${typeOf(value)}`);
|
|
831
|
-
if (depth < DEPTH$1 && value.length > 0) walk$1(value[0], `${path}[*]`, depth + 1, tree, lists);
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
if (isObject(value)) {
|
|
835
|
-
if (depth > 0) tree.push(`${path} object`);
|
|
836
|
-
if (depth >= DEPTH$1) return;
|
|
837
|
-
for (const [key, child] of Object.entries(value).slice(0, KEY_LIMIT)) walk$1(child, childPath(path, key), depth + 1, tree, lists);
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
tree.push(`${path} ${typeOf(value)}${sample(value)}`);
|
|
841
|
-
}
|
|
842
|
-
function typeOf(value) {
|
|
843
|
-
if (Array.isArray(value)) {
|
|
844
|
-
const kinds = [...new Set(value.slice(0, SAMPLE).map(entry => typeOf(entry).split(' ', 1)[0]))];
|
|
845
|
-
return `array (${value.length})${kinds.length === 0 ? '' : ` of ${kinds.join(' | ')}`}`;
|
|
846
|
-
}
|
|
847
|
-
if (value === null) return 'null';
|
|
848
|
-
return typeof value === 'object' ? 'object' : typeof value;
|
|
849
|
-
}
|
|
850
|
-
function sample(value) {
|
|
851
|
-
if (value === null || value === undefined) return '';
|
|
852
|
-
const text = JSON.stringify(value);
|
|
853
|
-
return ` ${text.length > SAMPLE_CHARS$1 ? `${text.slice(0, SAMPLE_CHARS$1)}…` : text}`;
|
|
854
|
-
}
|
|
855
|
-
function sharedKeys(objects) {
|
|
856
|
-
const [first, ...rest] = objects;
|
|
857
|
-
return Object.keys(first).filter(key => rest.every(object => Object.hasOwn(object, key)));
|
|
858
|
-
}
|
|
859
|
-
function childPath(path, key) {
|
|
860
|
-
return /^[$A-Z_][\w$]*$/i.test(key) ? `${path}.${key}` : `${path}['${key.replaceAll("'", String.raw`\'`)}']`;
|
|
861
|
-
}
|
|
862
|
-
function isObject(value) {
|
|
863
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
/** Rows shown in a probe, at most. */
|
|
867
|
-
const ROW_LIMIT = 80;
|
|
868
|
-
/**
|
|
869
|
-
* Summarises a PDF for someone writing a recipe: what the rows look like, and
|
|
870
|
-
* where the tables start, so a `table` extract's selector and columns can be
|
|
871
|
-
* read off the result instead of guessed.
|
|
872
|
-
*
|
|
873
|
-
* @param document - The read PDF.
|
|
874
|
-
* @returns The findings.
|
|
875
|
-
*/
|
|
876
|
-
function describePdf(document) {
|
|
877
|
-
const rows = document.pages.flatMap(page => page.rows.map((row, index) => ({
|
|
878
|
-
page: page.number,
|
|
879
|
-
row,
|
|
880
|
-
next: page.rows.slice(index + 1, index + 3)
|
|
881
|
-
})));
|
|
882
|
-
const headers = rows.filter(({
|
|
883
|
-
row,
|
|
884
|
-
next
|
|
885
|
-
}) => isHeaderRow(row.cells.map(cell => cell.text)) && next.some(candidate => isDataRow(candidate.cells.map(cell => cell.text))));
|
|
886
|
-
return {
|
|
887
|
-
pages: document.pages.length,
|
|
888
|
-
rows: rows.slice(0, ROW_LIMIT).map(({
|
|
889
|
-
page,
|
|
890
|
-
row
|
|
891
|
-
}) => ({
|
|
892
|
-
page,
|
|
893
|
-
text: row.cells.map(cell => cell.text).join(' | ')
|
|
894
|
-
})),
|
|
895
|
-
headers: headers.map(({
|
|
896
|
-
page,
|
|
897
|
-
row
|
|
898
|
-
}) => ({
|
|
899
|
-
page,
|
|
900
|
-
text: row.cells.map(cell => cell.text).join(' | '),
|
|
901
|
-
selector: `^${escape(row.cells[0].text)}`
|
|
902
|
-
}))
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
function escape(text) {
|
|
906
|
-
return text.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
462
|
/**
|
|
910
463
|
* Renders a probe's findings as text.
|
|
911
464
|
*
|
|
@@ -999,86 +552,6 @@ function section(title, rows) {
|
|
|
999
552
|
return [`${title}:`, ...rows, ''].join('\n');
|
|
1000
553
|
}
|
|
1001
554
|
|
|
1002
|
-
/** How deep a probe describes an XML document. */
|
|
1003
|
-
const DEPTH = 5;
|
|
1004
|
-
/** Distinct paths shown, at most. */
|
|
1005
|
-
const PATH_LIMIT = 60;
|
|
1006
|
-
/** A sample value longer than this is cut. */
|
|
1007
|
-
const SAMPLE_CHARS = 40;
|
|
1008
|
-
const ELEMENT = 1;
|
|
1009
|
-
/**
|
|
1010
|
-
* Summarises an XML document for someone writing a recipe: its namespaces
|
|
1011
|
-
* (and the `namespaces` a query needs), the elements that repeat (the
|
|
1012
|
-
* records a `forEach` walks), and its structure.
|
|
1013
|
-
*
|
|
1014
|
-
* @param document - A parsed document (`parseXml` from the core), read through the W3C DOM properties.
|
|
1015
|
-
* @returns The findings.
|
|
1016
|
-
*/
|
|
1017
|
-
function describeXml(document) {
|
|
1018
|
-
const root = document.documentElement;
|
|
1019
|
-
if (root === null) return {
|
|
1020
|
-
root: '',
|
|
1021
|
-
namespaces: {},
|
|
1022
|
-
lists: [],
|
|
1023
|
-
tree: []
|
|
1024
|
-
};
|
|
1025
|
-
const paths = new Map();
|
|
1026
|
-
walk(root, `/${root.localName ?? ''}`, 1, paths);
|
|
1027
|
-
const namespaces = Object.fromEntries(Array.from(root.attributes ?? [], attribute => attribute).filter(attribute => attribute.name === 'xmlns' || attribute.prefix === 'xmlns').map(attribute => [attribute.name === 'xmlns' ? '' : attribute.localName, attribute.value]));
|
|
1028
|
-
const lists = [...paths].filter(([, info]) => info.maxSiblings >= 2).map(([path, info]) => ({
|
|
1029
|
-
path: `//${path.split('/').slice(-2).join('/')}`.replace(/^\/\/\//, '//'),
|
|
1030
|
-
count: info.count,
|
|
1031
|
-
children: [...info.children]
|
|
1032
|
-
})).sort((first, second) => second.count - first.count);
|
|
1033
|
-
const tree = [...paths].slice(0, PATH_LIMIT).map(([path, info]) => `${path}${info.count > 1 ? ` ×${info.count}` : ''}${info.attributes.size > 0 ? ` ${[...info.attributes].map(name => `@${name}`).join(' ')}` : ''}${info.sample === undefined ? '' : ` ${JSON.stringify(info.sample)}`}`);
|
|
1034
|
-
const name = root.localName ?? '';
|
|
1035
|
-
const sitemap = name === 'urlset' || name === 'sitemapindex' ? {
|
|
1036
|
-
kind: name,
|
|
1037
|
-
locations: paths.get(`/${name}/${name === 'urlset' ? 'url' : 'sitemap'}/loc`)?.count ?? 0
|
|
1038
|
-
} : undefined;
|
|
1039
|
-
return {
|
|
1040
|
-
root: name,
|
|
1041
|
-
namespaces,
|
|
1042
|
-
lists,
|
|
1043
|
-
tree,
|
|
1044
|
-
...(sitemap !== undefined && {
|
|
1045
|
-
sitemap
|
|
1046
|
-
})
|
|
1047
|
-
};
|
|
1048
|
-
}
|
|
1049
|
-
function walk(element, path, depth, paths) {
|
|
1050
|
-
const info = paths.get(path) ?? {
|
|
1051
|
-
count: 0,
|
|
1052
|
-
attributes: new Set(),
|
|
1053
|
-
children: new Set(),
|
|
1054
|
-
maxSiblings: 1
|
|
1055
|
-
};
|
|
1056
|
-
paths.set(path, info);
|
|
1057
|
-
info.count += 1;
|
|
1058
|
-
const attributes = element.attributes ?? [];
|
|
1059
|
-
for (const attribute of attributes) {
|
|
1060
|
-
if (attribute.name !== 'xmlns' && attribute.prefix !== 'xmlns') info.attributes.add(attribute.name);
|
|
1061
|
-
}
|
|
1062
|
-
const children = [...element.childNodes].filter(child => child.nodeType === ELEMENT);
|
|
1063
|
-
if (children.length === 0) {
|
|
1064
|
-
const text = (element.textContent ?? '').replaceAll(/\s+/g, ' ').trim();
|
|
1065
|
-
if (text !== '' && info.sample === undefined) info.sample = text.length > SAMPLE_CHARS ? `${text.slice(0, SAMPLE_CHARS)}…` : text;
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
const siblings = new Map();
|
|
1069
|
-
for (const child of children) {
|
|
1070
|
-
const name = child.localName ?? '';
|
|
1071
|
-
info.children.add(name);
|
|
1072
|
-
siblings.set(name, (siblings.get(name) ?? 0) + 1);
|
|
1073
|
-
}
|
|
1074
|
-
for (const child of children) {
|
|
1075
|
-
const childPath = `${path}/${child.localName ?? ''}`;
|
|
1076
|
-
if (depth < DEPTH) walk(child, childPath, depth + 1, paths);
|
|
1077
|
-
const childInfo = paths.get(childPath);
|
|
1078
|
-
if (childInfo !== undefined) childInfo.maxSiblings = Math.max(childInfo.maxSiblings, siblings.get(child.localName ?? '') ?? 1);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
555
|
const BROWSER_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0 Safari/537.36';
|
|
1083
556
|
const OBSERVE_MS = 4000;
|
|
1084
557
|
/**
|
|
@@ -1556,5 +1029,5 @@ function processTerminal() {
|
|
|
1556
1029
|
};
|
|
1557
1030
|
}
|
|
1558
1031
|
|
|
1559
|
-
export { USAGE, diffReport,
|
|
1032
|
+
export { USAGE, diffReport, loadForRun, loadHooks, loadPlugins, main, parseArguments, probeReport, probeUrl, processTerminal, readRecipeFiles, resolveAccess, runStudio, writeChanges };
|
|
1560
1033
|
//# sourceMappingURL=index.esm.js.map
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
export { probePage, probeUrl } from './probe-page.use-case.js';
|
|
2
2
|
export type { ProbeResult, ProbeOptions } from './probe-page.use-case.js';
|
|
3
|
-
export { findData } from './find-data.algorithm.js';
|
|
4
|
-
export type { ProbeFindings, JsonLdFinding, InlineJsonFinding } from './find-data.algorithm.js';
|
|
5
3
|
export { probeReport, pdfReport, workbookReport, deckReport, jsonReport, xmlReport } from './probe-report.mapper.js';
|
|
6
|
-
export {
|
|
7
|
-
export type {
|
|
8
|
-
export { describePdf } from './pdf-findings.mapper.js';
|
|
9
|
-
export type { PdfFindings } from './pdf-findings.mapper.js';
|
|
10
|
-
export { describeWorkbook } from './workbook-findings.mapper.js';
|
|
11
|
-
export type { WorkbookFindings } from './workbook-findings.mapper.js';
|
|
12
|
-
export { describeDeck } from './deck-findings.mapper.js';
|
|
13
|
-
export type { DeckFindings } from './deck-findings.mapper.js';
|
|
14
|
-
export { describeJson } from './json-findings.mapper.js';
|
|
15
|
-
export { describeHtml } from './html-findings.mapper.js';
|
|
16
|
-
export type { HtmlFindings } from './html-findings.mapper.js';
|
|
17
|
-
export type { JsonFindings } from './json-findings.mapper.js';
|
|
4
|
+
export { findData } from '@opencraw/probe';
|
|
5
|
+
export type { ProbeFindings, JsonLdFinding, InlineJsonFinding } from '@opencraw/probe';
|
|
18
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type { AccessConfig, AccessPlugin } from '@opencraw/core';
|
|
2
|
+
import { findData } from '@opencraw/probe';
|
|
3
|
+
import type { DeckFindings, HtmlFindings, JsonFindings, PdfFindings, WorkbookFindings, XmlFindings } from '@opencraw/probe';
|
|
2
4
|
import type { CommonOptions } from '../arguments/index.js';
|
|
3
5
|
import type { Terminal } from '../terminal/index.js';
|
|
4
|
-
import { findData } from './find-data.algorithm.js';
|
|
5
|
-
import type { DeckFindings } from './deck-findings.mapper.js';
|
|
6
|
-
import type { HtmlFindings } from './html-findings.mapper.js';
|
|
7
|
-
import type { JsonFindings } from './json-findings.mapper.js';
|
|
8
|
-
import type { PdfFindings } from './pdf-findings.mapper.js';
|
|
9
|
-
import type { XmlFindings } from './xml-findings.mapper.js';
|
|
10
|
-
import type { WorkbookFindings } from './workbook-findings.mapper.js';
|
|
11
6
|
/** What fetching and, optionally, rendering a page found. */
|
|
12
7
|
export interface ProbeResult {
|
|
13
8
|
url: string;
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type { ProbeFindings } from '
|
|
2
|
-
import type { DeckFindings } from './deck-findings.mapper.js';
|
|
3
|
-
import type { HtmlFindings } from './html-findings.mapper.js';
|
|
4
|
-
import type { JsonFindings } from './json-findings.mapper.js';
|
|
5
|
-
import type { XmlFindings } from './xml-findings.mapper.js';
|
|
6
|
-
import type { PdfFindings } from './pdf-findings.mapper.js';
|
|
7
|
-
import type { WorkbookFindings } from './workbook-findings.mapper.js';
|
|
1
|
+
import type { DeckFindings, HtmlFindings, JsonFindings, PdfFindings, ProbeFindings, WorkbookFindings, XmlFindings } from '@opencraw/probe';
|
|
8
2
|
/**
|
|
9
3
|
* Renders a probe's findings as text.
|
|
10
4
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencraw/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.esm.js",
|
|
6
6
|
"module": "./dist/index.esm.js",
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
"!**/*.js.map"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@opencraw/core": "^0.1.
|
|
27
|
+
"@opencraw/core": "^0.1.19",
|
|
28
|
+
"@opencraw/probe": "^0.0.2",
|
|
28
29
|
"playwright": "^1.63.0"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|
|
31
|
-
"@opencraw/studio": "^0.0.
|
|
32
|
+
"@opencraw/studio": "^0.0.8"
|
|
32
33
|
},
|
|
33
34
|
"peerDependenciesMeta": {
|
|
34
35
|
"@opencraw/studio": {
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { DeckDocument } from '@opencraw/core';
|
|
2
|
-
/** What a probe shows of a deck (a presentation). */
|
|
3
|
-
export interface DeckFindings {
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
slides: {
|
|
7
|
-
number: number;
|
|
8
|
-
title: string;
|
|
9
|
-
hidden: boolean;
|
|
10
|
-
shapes: number;
|
|
11
|
-
tables: number;
|
|
12
|
-
charts: number;
|
|
13
|
-
}[];
|
|
14
|
-
/** The likely header rows of native tables, with the `slide` and `selector` a `table` extract needs. */
|
|
15
|
-
headers: {
|
|
16
|
-
slide: number;
|
|
17
|
-
text: string;
|
|
18
|
-
selector: string;
|
|
19
|
-
hint?: string;
|
|
20
|
-
}[];
|
|
21
|
-
charts: {
|
|
22
|
-
slide: number;
|
|
23
|
-
type: string;
|
|
24
|
-
title?: string;
|
|
25
|
-
series: {
|
|
26
|
-
name: string;
|
|
27
|
-
points: number;
|
|
28
|
-
}[];
|
|
29
|
-
}[];
|
|
30
|
-
/** Slides whose short text boxes look like a table: read them with `"shapes": true`. */
|
|
31
|
-
grids: {
|
|
32
|
-
slide: number;
|
|
33
|
-
title: string;
|
|
34
|
-
boxes: number;
|
|
35
|
-
}[];
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Summarises a deck for someone writing a recipe: its slides, where the native
|
|
39
|
-
* tables start, what the charts hold, and which slides lay text boxes out as
|
|
40
|
-
* a table.
|
|
41
|
-
*
|
|
42
|
-
* @param document - The read deck.
|
|
43
|
-
* @returns The findings.
|
|
44
|
-
*/
|
|
45
|
-
export declare function describeDeck(document: DeckDocument): DeckFindings;
|
|
46
|
-
//# sourceMappingURL=deck-findings.mapper.d.ts.map
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Finds where a fetched page's data lives: JSON-LD blocks, inline JSON objects
|
|
3
|
-
* big enough to matter, URLs that look like a data endpoint, script hosts and
|
|
4
|
-
* links whose path suggests an API. Pattern-matching only; it never fetches.
|
|
5
|
-
*/
|
|
6
|
-
export interface JsonLdFinding {
|
|
7
|
-
/** The `@type` value(s), or `'?'` when the block has none. */
|
|
8
|
-
types: string;
|
|
9
|
-
/** How many top-level keys the block has. */
|
|
10
|
-
keys: number;
|
|
11
|
-
}
|
|
12
|
-
export interface InlineJsonFinding {
|
|
13
|
-
/** Where it was found: a `<script>` with no `type`, or an attribute. */
|
|
14
|
-
where: string;
|
|
15
|
-
keys: string[];
|
|
16
|
-
/** Serialized size, for ranking. */
|
|
17
|
-
size: number;
|
|
18
|
-
}
|
|
19
|
-
export interface ProbeFindings {
|
|
20
|
-
jsonLd: JsonLdFinding[];
|
|
21
|
-
inlineJson: InlineJsonFinding[];
|
|
22
|
-
/** URLs referenced in the page whose path ends in `.json`. */
|
|
23
|
-
jsonUrls: string[];
|
|
24
|
-
/** Distinct hosts the page loads a `<script src>` from. */
|
|
25
|
-
scriptHosts: string[];
|
|
26
|
-
/** Links whose path contains `api`. */
|
|
27
|
-
apiLinks: string[];
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Scans a page's HTML for data-shaped things.
|
|
31
|
-
*
|
|
32
|
-
* @param html - The document.
|
|
33
|
-
* @returns What was found, each list capped at a handful of entries.
|
|
34
|
-
*/
|
|
35
|
-
export declare function findData(html: string): ProbeFindings;
|
|
36
|
-
//# sourceMappingURL=find-data.algorithm.d.ts.map
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/** What a probe shows of an HTML document's tables, and of rendered Markdown's sections and front matter. */
|
|
2
|
-
export interface HtmlFindings {
|
|
3
|
-
/** The likely header rows of the `<table>`s, with the `selector` a `table` extract needs. */
|
|
4
|
-
tables: {
|
|
5
|
-
table: string;
|
|
6
|
-
text: string;
|
|
7
|
-
selector: string;
|
|
8
|
-
hint?: string;
|
|
9
|
-
}[];
|
|
10
|
-
/** Rendered Markdown: its headings, each with the selector of its section. */
|
|
11
|
-
outline?: {
|
|
12
|
-
level: number;
|
|
13
|
-
heading: string;
|
|
14
|
-
selector: string;
|
|
15
|
-
}[];
|
|
16
|
-
/** Rendered Markdown: the keys of its front matter. */
|
|
17
|
-
frontMatter?: string[];
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Summarises an HTML document for a `table` extract, and, when it is rendered
|
|
21
|
-
* Markdown or a Word document, its outline (and Markdown's front matter).
|
|
22
|
-
*
|
|
23
|
-
* @param html - The document.
|
|
24
|
-
* @param markdown - Whether it was rendered from Markdown or Word, and so has sections.
|
|
25
|
-
* @returns The findings.
|
|
26
|
-
*/
|
|
27
|
-
export declare function describeHtml(html: string, markdown: boolean): HtmlFindings;
|
|
28
|
-
//# sourceMappingURL=html-findings.mapper.d.ts.map
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/** What a probe shows of JSON data (a JSON, JSON Lines or YAML body). */
|
|
2
|
-
export interface JsonFindings {
|
|
3
|
-
/** What the body was read as. */
|
|
4
|
-
format: string;
|
|
5
|
-
/** `object`, `array (n)`, or a scalar type. */
|
|
6
|
-
type: string;
|
|
7
|
-
/**
|
|
8
|
-
* The document's structure down to four levels, one line per node:
|
|
9
|
-
* `$.data.products array (1240) of object`, `$.data.products[*].name string "Pandina"`.
|
|
10
|
-
*/
|
|
11
|
-
tree: string[];
|
|
12
|
-
/**
|
|
13
|
-
* Every array of objects with two entries or more, largest first: the
|
|
14
|
-
* `forEach` or `each` a recipe needs, with the keys all its entries share.
|
|
15
|
-
*/
|
|
16
|
-
lists: {
|
|
17
|
-
path: string;
|
|
18
|
-
length: number;
|
|
19
|
-
keys: string[];
|
|
20
|
-
}[];
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Summarises JSON data for someone writing a recipe: its shape, and where its
|
|
24
|
-
* record lists are, so a `jsonpath` selector can be read off the result.
|
|
25
|
-
*
|
|
26
|
-
* @param data - The parsed document.
|
|
27
|
-
* @param format - What it was read as (`json`, `jsonl`, `yaml`).
|
|
28
|
-
* @returns The findings.
|
|
29
|
-
*/
|
|
30
|
-
export declare function describeJson(data: unknown, format: string): JsonFindings;
|
|
31
|
-
//# sourceMappingURL=json-findings.mapper.d.ts.map
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { PdfDocument } from '@opencraw/core';
|
|
2
|
-
/** What a probe shows of a PDF: its rows, and the rows that look like table headers. */
|
|
3
|
-
export interface PdfFindings {
|
|
4
|
-
pages: number;
|
|
5
|
-
/** The first rows, cells separated by " | ". */
|
|
6
|
-
rows: {
|
|
7
|
-
page: number;
|
|
8
|
-
text: string;
|
|
9
|
-
}[];
|
|
10
|
-
/** Rows of text cells (no digit) with a row of data (a digit) right under them: likely table headers, with a selector that finds them. */
|
|
11
|
-
headers: {
|
|
12
|
-
page: number;
|
|
13
|
-
text: string;
|
|
14
|
-
selector: string;
|
|
15
|
-
}[];
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Summarises a PDF for someone writing a recipe: what the rows look like, and
|
|
19
|
-
* where the tables start, so a `table` extract's selector and columns can be
|
|
20
|
-
* read off the result instead of guessed.
|
|
21
|
-
*
|
|
22
|
-
* @param document - The read PDF.
|
|
23
|
-
* @returns The findings.
|
|
24
|
-
*/
|
|
25
|
-
export declare function describePdf(document: PdfDocument): PdfFindings;
|
|
26
|
-
//# sourceMappingURL=pdf-findings.mapper.d.ts.map
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Whether a cell holds a figure (an amount, a count, a date written in
|
|
3
|
-
* digits) rather than words: it starts with a number, or its digits are at
|
|
4
|
-
* least as many as its letters. `2,820`, `$560`, `2659 $560`, `(£0.58m)`,
|
|
5
|
-
* `-3 days` and `2026` are figures; `FY26 Lodging Rate`,
|
|
6
|
-
* `FM Classes A, B1 & C3` and `(APRIL 1, 2020)` are words that happen to
|
|
7
|
-
* contain digits.
|
|
8
|
-
*
|
|
9
|
-
* @param cell - The cell's text.
|
|
10
|
-
* @returns Whether it is a figure.
|
|
11
|
-
*/
|
|
12
|
-
export declare function isFigure(cell: string): boolean;
|
|
13
|
-
/**
|
|
14
|
-
* A row that reads like a table's header: at least two cells, none a figure.
|
|
15
|
-
*
|
|
16
|
-
* @param cells - The row's non-empty cells.
|
|
17
|
-
* @returns Whether it could be a header.
|
|
18
|
-
*/
|
|
19
|
-
export declare function isHeaderRow(cells: readonly string[]): boolean;
|
|
20
|
-
/**
|
|
21
|
-
* A row that reads like a table's data: at least two cells, one a figure.
|
|
22
|
-
*
|
|
23
|
-
* @param cells - The row's non-empty cells.
|
|
24
|
-
* @returns Whether it looks like data.
|
|
25
|
-
*/
|
|
26
|
-
export declare function isDataRow(cells: readonly string[]): boolean;
|
|
27
|
-
//# sourceMappingURL=table-header.policy.d.ts.map
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { WorkbookDocument } from '@opencraw/core';
|
|
2
|
-
/** What a probe shows of a workbook (a spreadsheet or a CSV). */
|
|
3
|
-
export interface WorkbookFindings {
|
|
4
|
-
/** How a CSV was read. */
|
|
5
|
-
csv?: {
|
|
6
|
-
encoding: string;
|
|
7
|
-
delimiter: string;
|
|
8
|
-
};
|
|
9
|
-
sheets: {
|
|
10
|
-
name: string;
|
|
11
|
-
hidden: boolean;
|
|
12
|
-
rows: number;
|
|
13
|
-
columns: number;
|
|
14
|
-
}[];
|
|
15
|
-
/** The first non-empty rows of each visible sheet, cells separated by " | ". */
|
|
16
|
-
rows: {
|
|
17
|
-
sheet: string;
|
|
18
|
-
row: number;
|
|
19
|
-
text: string;
|
|
20
|
-
}[];
|
|
21
|
-
/**
|
|
22
|
-
* Rows of words (no cell a figure) with a data row (a figure) just below:
|
|
23
|
-
* likely table headers, with the `sheet` and `selector` a `table` extract
|
|
24
|
-
* needs, and a hint when the header has merged cells (a header over two
|
|
25
|
-
* rows). The rows right under a header are its table's body, not headers.
|
|
26
|
-
*/
|
|
27
|
-
headers: {
|
|
28
|
-
sheet: string;
|
|
29
|
-
row: number;
|
|
30
|
-
text: string;
|
|
31
|
-
selector: string;
|
|
32
|
-
hint?: string;
|
|
33
|
-
}[];
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Summarises a workbook for someone writing a recipe: its sheets, what their
|
|
37
|
-
* rows look like and where the tables start, so a `table` extract's `sheet`,
|
|
38
|
-
* `selector` and columns can be read off the result instead of guessed.
|
|
39
|
-
*
|
|
40
|
-
* @param document - The read workbook.
|
|
41
|
-
* @returns The findings.
|
|
42
|
-
*/
|
|
43
|
-
export declare function describeWorkbook(document: WorkbookDocument): WorkbookFindings;
|
|
44
|
-
//# sourceMappingURL=workbook-findings.mapper.d.ts.map
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** What a probe shows of an XML document. */
|
|
2
|
-
export interface XmlFindings {
|
|
3
|
-
/** The root element's local name. */
|
|
4
|
-
root: string;
|
|
5
|
-
/** Declared on the root: prefix (`''` for the default namespace) to URI. */
|
|
6
|
-
namespaces: Record<string, string>;
|
|
7
|
-
/** Elements that repeat under one parent, largest first: the records, with the child names they share. */
|
|
8
|
-
lists: {
|
|
9
|
-
path: string;
|
|
10
|
-
count: number;
|
|
11
|
-
children: string[];
|
|
12
|
-
}[];
|
|
13
|
-
/** One line per distinct element path (local names), with its count, attributes and a sample text. */
|
|
14
|
-
tree: string[];
|
|
15
|
-
/** For a sitemap (`urlset`, `sitemapindex`): how many locations it lists. */
|
|
16
|
-
sitemap?: {
|
|
17
|
-
kind: 'urlset' | 'sitemapindex';
|
|
18
|
-
locations: number;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Summarises an XML document for someone writing a recipe: its namespaces
|
|
23
|
-
* (and the `namespaces` a query needs), the elements that repeat (the
|
|
24
|
-
* records a `forEach` walks), and its structure.
|
|
25
|
-
*
|
|
26
|
-
* @param document - A parsed document (`parseXml` from the core), read through the W3C DOM properties.
|
|
27
|
-
* @returns The findings.
|
|
28
|
-
*/
|
|
29
|
-
export declare function describeXml(document: unknown): XmlFindings;
|
|
30
|
-
//# sourceMappingURL=xml-findings.mapper.d.ts.map
|