@json-to-office/core-docx 2.1.0 → 2.4.0
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.js +462 -29
- package/dist/index.js.map +1 -1
- package/dist/plugin/example/index.js +274 -27
- package/dist/plugin/example/index.js.map +1 -1
- package/dist/plugin/validation.d.ts.map +1 -1
- package/dist/quality/facts.d.ts +56 -2
- package/dist/quality/facts.d.ts.map +1 -1
- package/dist/quality/rules.d.ts +16 -0
- package/dist/quality/rules.d.ts.map +1 -1
- package/dist/themes/defaults.d.ts +13 -1
- package/dist/themes/defaults.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -11,6 +11,7 @@ var __export = (target, all) => {
|
|
|
11
11
|
// src/themes/defaults.ts
|
|
12
12
|
function ensureThemeDefaults(theme) {
|
|
13
13
|
return {
|
|
14
|
+
...theme,
|
|
14
15
|
$schema: theme.$schema,
|
|
15
16
|
name: theme.name || "default",
|
|
16
17
|
displayName: theme.displayName || "Default Theme",
|
|
@@ -21,6 +22,7 @@ function ensureThemeDefaults(theme) {
|
|
|
21
22
|
...theme.colors || {}
|
|
22
23
|
},
|
|
23
24
|
fonts: {
|
|
25
|
+
...theme.fonts || {},
|
|
24
26
|
heading: { ...DEFAULT_FONTS.heading, ...theme.fonts?.heading || {} },
|
|
25
27
|
body: { ...DEFAULT_FONTS.body, ...theme.fonts?.body || {} },
|
|
26
28
|
mono: { ...DEFAULT_FONTS.mono, ...theme.fonts?.mono || {} },
|
|
@@ -4619,34 +4621,36 @@ function validateDocument(document, customComponents, options) {
|
|
|
4619
4621
|
const errors = [...documentResult.errors || []];
|
|
4620
4622
|
function validateComponents(components, pathPrefix = "children") {
|
|
4621
4623
|
components.forEach((componentData, index) => {
|
|
4622
|
-
|
|
4623
|
-
(cc) => cc.name === componentData.name
|
|
4624
|
-
);
|
|
4625
|
-
if (!customComponent) {
|
|
4624
|
+
if (!componentData || typeof componentData !== "object" || Array.isArray(componentData)) {
|
|
4626
4625
|
return;
|
|
4627
4626
|
}
|
|
4628
|
-
const
|
|
4629
|
-
|
|
4630
|
-
customComponent.versions,
|
|
4631
|
-
componentData.version
|
|
4632
|
-
);
|
|
4633
|
-
const validation = validateComponentProps(
|
|
4634
|
-
versionEntry,
|
|
4635
|
-
componentData.props,
|
|
4636
|
-
customComponent.name,
|
|
4637
|
-
// Reject unknown custom props unless the caller allows them.
|
|
4638
|
-
{ clean: options?.allowUnknownFields === true }
|
|
4627
|
+
const customComponent = customComponents.find(
|
|
4628
|
+
(cc) => cc.name === componentData.name
|
|
4639
4629
|
);
|
|
4640
|
-
if (
|
|
4641
|
-
const
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4630
|
+
if (customComponent) {
|
|
4631
|
+
const versionEntry = resolveComponentVersion(
|
|
4632
|
+
customComponent.name,
|
|
4633
|
+
customComponent.versions,
|
|
4634
|
+
componentData.version
|
|
4635
|
+
);
|
|
4636
|
+
const validation = validateComponentProps(
|
|
4637
|
+
versionEntry,
|
|
4638
|
+
componentData.props,
|
|
4639
|
+
customComponent.name,
|
|
4640
|
+
// Reject unknown custom props unless the caller allows them.
|
|
4641
|
+
{ clean: options?.allowUnknownFields === true }
|
|
4646
4642
|
);
|
|
4647
|
-
errors
|
|
4643
|
+
if (!validation.valid && validation.errors) {
|
|
4644
|
+
const indexedErrors = validation.errors.map(
|
|
4645
|
+
(error) => ({
|
|
4646
|
+
...error,
|
|
4647
|
+
path: `${pathPrefix}[${index}].${error.path}`
|
|
4648
|
+
})
|
|
4649
|
+
);
|
|
4650
|
+
errors.push(...indexedErrors);
|
|
4651
|
+
}
|
|
4648
4652
|
}
|
|
4649
|
-
if (
|
|
4653
|
+
if (Array.isArray(componentData.children)) {
|
|
4650
4654
|
validateComponents(
|
|
4651
4655
|
componentData.children,
|
|
4652
4656
|
`${pathPrefix}[${index}].children`
|
|
@@ -11475,6 +11479,15 @@ function createRenderContext(document, theme, themeName) {
|
|
|
11475
11479
|
}
|
|
11476
11480
|
|
|
11477
11481
|
// src/quality/facts.ts
|
|
11482
|
+
import {
|
|
11483
|
+
chartEncodingFor,
|
|
11484
|
+
collectColorLiterals,
|
|
11485
|
+
collectFontFamilies,
|
|
11486
|
+
collectPlaceholders,
|
|
11487
|
+
hasUnitMarker,
|
|
11488
|
+
normalizeHex,
|
|
11489
|
+
normalizeHighchartsChart
|
|
11490
|
+
} from "@json-to-office/quality";
|
|
11478
11491
|
import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
|
|
11479
11492
|
|
|
11480
11493
|
// src/json/normalizer.ts
|
|
@@ -11623,6 +11636,164 @@ function tableFact(props, path3, availableWidthTwips) {
|
|
|
11623
11636
|
explicitWidths
|
|
11624
11637
|
};
|
|
11625
11638
|
}
|
|
11639
|
+
var SERIES_COLOR_TOKENS = [
|
|
11640
|
+
"primary",
|
|
11641
|
+
"accent",
|
|
11642
|
+
"secondary",
|
|
11643
|
+
"accent4",
|
|
11644
|
+
"accent5",
|
|
11645
|
+
"accent6"
|
|
11646
|
+
];
|
|
11647
|
+
function chartFact(node, props, path3, paletteTokens) {
|
|
11648
|
+
const base = {
|
|
11649
|
+
id: `docx:chart:${path3}`,
|
|
11650
|
+
kind: "docx/chart",
|
|
11651
|
+
path: path3,
|
|
11652
|
+
componentName: typeof node.name === "string" ? node.name : "",
|
|
11653
|
+
paletteTokens
|
|
11654
|
+
};
|
|
11655
|
+
if (node.name === "highcharts") {
|
|
11656
|
+
const shape = normalizeHighchartsChart(props.options);
|
|
11657
|
+
return {
|
|
11658
|
+
...base,
|
|
11659
|
+
chartType: shape.chartType,
|
|
11660
|
+
encoding: shape.encoding,
|
|
11661
|
+
threeD: shape.threeD,
|
|
11662
|
+
seriesCount: shape.seriesCount,
|
|
11663
|
+
categoryCount: shape.categoryCount,
|
|
11664
|
+
seriesColorsStated: shape.seriesColorsStated,
|
|
11665
|
+
seriesColorsPath: `${path3}/props/options`,
|
|
11666
|
+
...shape.valueAxisMin !== void 0 && {
|
|
11667
|
+
valueAxisMin: shape.valueAxisMin
|
|
11668
|
+
},
|
|
11669
|
+
unitStated: shape.unitStated,
|
|
11670
|
+
annotation: {
|
|
11671
|
+
stated: shape.annotationStated,
|
|
11672
|
+
path: path3,
|
|
11673
|
+
slot: "props.options.caption.text"
|
|
11674
|
+
}
|
|
11675
|
+
};
|
|
11676
|
+
}
|
|
11677
|
+
const chartType = typeof props.type === "string" ? props.type : "";
|
|
11678
|
+
if (chartType === "") return void 0;
|
|
11679
|
+
const series = Array.isArray(props.data) ? props.data : [];
|
|
11680
|
+
const categoryCount = Math.max(
|
|
11681
|
+
0,
|
|
11682
|
+
...series.map((entry) => {
|
|
11683
|
+
const record = asRecord(entry);
|
|
11684
|
+
const labels = Array.isArray(record?.labels) ? record.labels.length : 0;
|
|
11685
|
+
const values = Array.isArray(record?.values) ? record.values.length : 0;
|
|
11686
|
+
return Math.max(labels, values);
|
|
11687
|
+
})
|
|
11688
|
+
);
|
|
11689
|
+
const caption = typeof props.caption === "string" ? props.caption : "";
|
|
11690
|
+
const title = typeof props.title === "string" ? props.title : "";
|
|
11691
|
+
return {
|
|
11692
|
+
...base,
|
|
11693
|
+
chartType,
|
|
11694
|
+
encoding: chartEncodingFor(chartType),
|
|
11695
|
+
// No 3D type exists in the DOCX chart schema — `office-open` draws none —
|
|
11696
|
+
// so this is always false here, and stays a field rather than an omission
|
|
11697
|
+
// so both formats answer the same questions.
|
|
11698
|
+
threeD: false,
|
|
11699
|
+
seriesCount: series.length,
|
|
11700
|
+
categoryCount,
|
|
11701
|
+
seriesColorsStated: Array.isArray(props.chartColors) && props.chartColors.length > 0,
|
|
11702
|
+
seriesColorsPath: `${path3}/props/chartColors`,
|
|
11703
|
+
// No `valueAxisMin`: a DOCX chart has no axis-floor property, so the
|
|
11704
|
+
// baseline is always the renderer's, which starts a bar at zero.
|
|
11705
|
+
unitStated: hasUnitMarker(
|
|
11706
|
+
typeof props.valAxisTitle === "string" ? props.valAxisTitle : ""
|
|
11707
|
+
) || hasUnitMarker(title) || hasUnitMarker(caption),
|
|
11708
|
+
annotation: {
|
|
11709
|
+
stated: caption.trim() !== "",
|
|
11710
|
+
path: path3,
|
|
11711
|
+
slot: "props.caption"
|
|
11712
|
+
}
|
|
11713
|
+
};
|
|
11714
|
+
}
|
|
11715
|
+
var DOCX_ALIGNMENTS = /* @__PURE__ */ new Set(["left", "center", "right", "justify"]);
|
|
11716
|
+
function resolvedCellText(content) {
|
|
11717
|
+
return typeof content === "string" ? content : "[component]";
|
|
11718
|
+
}
|
|
11719
|
+
function statesOwnBorders(authored) {
|
|
11720
|
+
if (!authored) return false;
|
|
11721
|
+
if (authored.borderSize !== void 0 || authored.borderColor !== void 0 || authored.hideBorders !== void 0) {
|
|
11722
|
+
return true;
|
|
11723
|
+
}
|
|
11724
|
+
const layers = [
|
|
11725
|
+
asRecord(authored.cellDefaults),
|
|
11726
|
+
asRecord(authored.headerCellDefaults)
|
|
11727
|
+
];
|
|
11728
|
+
if (layers.some(
|
|
11729
|
+
(layer) => layer?.borderSize !== void 0 || layer?.borderColor !== void 0
|
|
11730
|
+
)) {
|
|
11731
|
+
return true;
|
|
11732
|
+
}
|
|
11733
|
+
const columns = Array.isArray(authored.columns) ? authored.columns : [];
|
|
11734
|
+
return columns.some((column) => {
|
|
11735
|
+
const record = asRecord(column);
|
|
11736
|
+
const defaults = asRecord(record?.cellDefaults);
|
|
11737
|
+
return defaults?.borderSize !== void 0 || defaults?.borderColor !== void 0;
|
|
11738
|
+
});
|
|
11739
|
+
}
|
|
11740
|
+
function tableDesignFact(props, path3, theme, themeName, authored) {
|
|
11741
|
+
const authoredColumns = Array.isArray(props.columns) ? props.columns : [];
|
|
11742
|
+
if (authoredColumns.length === 0) return void 0;
|
|
11743
|
+
const model = resolveTableModel(
|
|
11744
|
+
props,
|
|
11745
|
+
theme,
|
|
11746
|
+
themeName
|
|
11747
|
+
);
|
|
11748
|
+
const bodyRows = model.rows;
|
|
11749
|
+
const drawnRows = bodyRows.length + (model.header ? 1 : 0);
|
|
11750
|
+
if (drawnRows === 0) return void 0;
|
|
11751
|
+
const fullGrid = statesOwnBorders(authored) && [...model.header ? [model.header] : [], ...bodyRows].every(
|
|
11752
|
+
(row) => row.cells.every(
|
|
11753
|
+
(cell) => ["top", "right", "bottom", "left"].every(
|
|
11754
|
+
(side) => !cell.borders[side].hidden && cell.borders[side].size > 0
|
|
11755
|
+
)
|
|
11756
|
+
)
|
|
11757
|
+
);
|
|
11758
|
+
const columns = authoredColumns.map(
|
|
11759
|
+
(column, index) => {
|
|
11760
|
+
const authoredColumn = asRecord(column) ?? {};
|
|
11761
|
+
const cells = Array.isArray(authoredColumn.cells) ? authoredColumn.cells : [];
|
|
11762
|
+
const alignments = /* @__PURE__ */ new Set();
|
|
11763
|
+
const values = [];
|
|
11764
|
+
bodyRows.forEach((row) => {
|
|
11765
|
+
const cell = row.cells[index];
|
|
11766
|
+
if (!cell || cell.missing) return;
|
|
11767
|
+
values.push(resolvedCellText(cell.content));
|
|
11768
|
+
alignments.add(cell.horizontalAlignment);
|
|
11769
|
+
});
|
|
11770
|
+
const header = model.header?.cells[index];
|
|
11771
|
+
return {
|
|
11772
|
+
index,
|
|
11773
|
+
path: `${path3}/props/columns/${index}`,
|
|
11774
|
+
...header?.content !== void 0 && {
|
|
11775
|
+
header: resolvedCellText(header.content)
|
|
11776
|
+
},
|
|
11777
|
+
values,
|
|
11778
|
+
alignment: alignments.size === 1 ? [...alignments][0] : alignments.size === 0 ? "left" : "mixed",
|
|
11779
|
+
hasCellDefaults: asRecord(authoredColumn.cellDefaults) !== void 0,
|
|
11780
|
+
hasHeader: asRecord(authoredColumn.header) !== void 0,
|
|
11781
|
+
cellsWithOwnAlignment: cells.flatMap((cell, cellIndex) => {
|
|
11782
|
+
const alignment2 = asRecord(cell)?.horizontalAlignment;
|
|
11783
|
+
return typeof alignment2 === "string" && DOCX_ALIGNMENTS.has(alignment2) && alignment2 !== "right" ? [cellIndex] : [];
|
|
11784
|
+
})
|
|
11785
|
+
};
|
|
11786
|
+
}
|
|
11787
|
+
);
|
|
11788
|
+
return {
|
|
11789
|
+
id: `docx:table-design:${path3}`,
|
|
11790
|
+
kind: "docx/table",
|
|
11791
|
+
path: path3,
|
|
11792
|
+
columns,
|
|
11793
|
+
rowCount: drawnRows,
|
|
11794
|
+
fullGrid
|
|
11795
|
+
};
|
|
11796
|
+
}
|
|
11626
11797
|
var TWIPS_PER_POINT3 = 20;
|
|
11627
11798
|
var SINGLE_LINE_RATIO = 1.2;
|
|
11628
11799
|
function finiteNumber(value) {
|
|
@@ -11651,20 +11822,23 @@ function characterSpacingPt(spacing) {
|
|
|
11651
11822
|
if (rule === void 0 || value === void 0) return 0;
|
|
11652
11823
|
return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
|
|
11653
11824
|
}
|
|
11654
|
-
function
|
|
11825
|
+
function nodeAtPointer(root, pointer) {
|
|
11655
11826
|
let current = root;
|
|
11656
11827
|
for (const token of pointer.split("/").slice(1)) {
|
|
11657
11828
|
if (Array.isArray(current)) {
|
|
11658
11829
|
const index = Number(token);
|
|
11659
|
-
if (!Number.isInteger(index)) return
|
|
11830
|
+
if (!Number.isInteger(index)) return void 0;
|
|
11660
11831
|
current = current[index];
|
|
11661
11832
|
continue;
|
|
11662
11833
|
}
|
|
11663
11834
|
const record = asRecord(current);
|
|
11664
|
-
if (!record) return
|
|
11835
|
+
if (!record) return void 0;
|
|
11665
11836
|
current = record[token];
|
|
11666
11837
|
}
|
|
11667
|
-
return current
|
|
11838
|
+
return current;
|
|
11839
|
+
}
|
|
11840
|
+
function pointerExists(root, pointer) {
|
|
11841
|
+
return nodeAtPointer(root, pointer) !== void 0;
|
|
11668
11842
|
}
|
|
11669
11843
|
function styleKey(node, props) {
|
|
11670
11844
|
if (node.name === "heading") {
|
|
@@ -11845,6 +12019,67 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
11845
12019
|
...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
|
|
11846
12020
|
};
|
|
11847
12021
|
};
|
|
12022
|
+
const paletteHexes = {};
|
|
12023
|
+
for (const [token, value] of Object.entries(
|
|
12024
|
+
resolved.theme.colors ?? {}
|
|
12025
|
+
)) {
|
|
12026
|
+
if (typeof value !== "string") continue;
|
|
12027
|
+
const hex = normalizeHex(value);
|
|
12028
|
+
if (hex) paletteHexes[token] = hex;
|
|
12029
|
+
}
|
|
12030
|
+
const paletteTokens = SERIES_COLOR_TOKENS.filter(
|
|
12031
|
+
(token) => paletteHexes[token] !== void 0
|
|
12032
|
+
);
|
|
12033
|
+
const authoredPropsAt = (pointer) => asRecord(asRecord(nodeAtPointer(context.document, pointer))?.props);
|
|
12034
|
+
addFact({
|
|
12035
|
+
id: "docx:theme",
|
|
12036
|
+
kind: "docx/theme",
|
|
12037
|
+
path: "/props",
|
|
12038
|
+
themeName: context.themeName,
|
|
12039
|
+
paletteHexes,
|
|
12040
|
+
// `heading` and `body` only. A theme also names `mono` and `light`, but
|
|
12041
|
+
// those paint nothing until a component asks for them — counting an
|
|
12042
|
+
// unused `Courier New` against a document's family budget would flag a
|
|
12043
|
+
// report that only ever uses one typeface.
|
|
12044
|
+
fontFamilies: [
|
|
12045
|
+
...new Set(
|
|
12046
|
+
["heading", "body"].flatMap((role) => {
|
|
12047
|
+
const family = asRecord(
|
|
12048
|
+
resolved.theme.fonts?.[role]
|
|
12049
|
+
)?.family;
|
|
12050
|
+
return typeof family === "string" && family.trim() !== "" ? [family] : [];
|
|
12051
|
+
})
|
|
12052
|
+
)
|
|
12053
|
+
]
|
|
12054
|
+
});
|
|
12055
|
+
collectColorLiterals(document).forEach((literal, index) => {
|
|
12056
|
+
addFact({
|
|
12057
|
+
id: `docx:color:${index}:${literal.path}`,
|
|
12058
|
+
kind: "docx/color",
|
|
12059
|
+
path: literal.path,
|
|
12060
|
+
raw: literal.raw,
|
|
12061
|
+
hex: literal.hex
|
|
12062
|
+
});
|
|
12063
|
+
});
|
|
12064
|
+
collectFontFamilies(document).forEach((use, index) => {
|
|
12065
|
+
addFact({
|
|
12066
|
+
id: `docx:font:${index}:${use.path}`,
|
|
12067
|
+
kind: "docx/font-family",
|
|
12068
|
+
path: use.path,
|
|
12069
|
+
family: use.family
|
|
12070
|
+
});
|
|
12071
|
+
});
|
|
12072
|
+
collectPlaceholders(document).forEach((occurrence, index) => {
|
|
12073
|
+
addFact({
|
|
12074
|
+
id: `docx:placeholder:${index}:${occurrence.path}`,
|
|
12075
|
+
kind: "docx/placeholder",
|
|
12076
|
+
path: occurrence.path,
|
|
12077
|
+
text: occurrence.text,
|
|
12078
|
+
placeholderKind: occurrence.match.kind,
|
|
12079
|
+
pattern: occurrence.match.pattern,
|
|
12080
|
+
excerpt: occurrence.match.excerpt
|
|
12081
|
+
});
|
|
12082
|
+
});
|
|
11848
12083
|
let previousVisitPath;
|
|
11849
12084
|
let lastFrame;
|
|
11850
12085
|
let flowIndex = 0;
|
|
@@ -11854,6 +12089,18 @@ function prepareDocxQualityDocument(document, options = {}) {
|
|
|
11854
12089
|
if (node.name === "table") {
|
|
11855
12090
|
const fact = tableFact(props, path3, page.availableWidthTwips);
|
|
11856
12091
|
if (fact) addFact(fact);
|
|
12092
|
+
const design = tableDesignFact(
|
|
12093
|
+
props,
|
|
12094
|
+
path3,
|
|
12095
|
+
resolved.theme,
|
|
12096
|
+
context.themeName,
|
|
12097
|
+
authoredPropsAt(path3)
|
|
12098
|
+
);
|
|
12099
|
+
if (design) addFact(design);
|
|
12100
|
+
}
|
|
12101
|
+
if (node.name === "chart" || node.name === "highcharts") {
|
|
12102
|
+
const fact = chartFact(node, props, path3, paletteTokens);
|
|
12103
|
+
if (fact) addFact(fact);
|
|
11857
12104
|
}
|
|
11858
12105
|
if (node.name === "paragraph" || node.name === "text-box") {
|
|
11859
12106
|
const floating = asRecord(props.floating);
|