refactor: complete three-layer serial refactoring (knowledge→schema→renderer)

Step 1 — Knowledge base: perfect all reference/*.md with type, range, default, unit
- text.md: add DataOrPathProps, path/data, bullet deprecated params
- chart.md: add deprecated section, fix 19 params with missing elements
- slide.md: add bkgd→background deprecated mapping
- output.md: add masterSlide, presLayout

Step 2 — Schema: align schema/presentation.schema.json with KB
- Add catAxisItem and valAxisItem definitions with full sub-property constraints
- Fix 7 enum constraints (barDir, displayBlanksAs, bar3DShape, etc.)
- Add 4 missing fields (verbose, autoPageCharWeight, autoPageLineWeight, notes,
  masterSlide, presLayout)

Step 3 — Translation engine: transform renderers from passthrough to validation
- index.js: add normalizePosition, normalizeColor, validateEnum shared utilities
- All renderers: position validation, color hex normalization, enum validation
- Image: graceful failure handling (catch load errors, warn, continue)
- Chart: chartType enum validation, data parity check
- Table: colspan/rowspan integer validation, border array check
- Full verification: basic.json and full.json generate valid PPTX

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 13:10:01 +08:00
co-authored by Claude Sonnet 5
parent c1a9c2fcda
commit ec7a44d6fb
12 changed files with 538 additions and 21 deletions
+97 -10
View File
@@ -313,6 +313,8 @@
{
"type": "object",
"properties": {
"autoPageCharWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page character weight adjustment" },
"autoPageLineWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page line weight adjustment" },
"border": {
"oneOf": [
{ "$ref": "#/definitions/borderProps" },
@@ -364,7 +366,8 @@
{ "type": "number" },
{ "type": "array", "items": { "type": "number" } }
]
}
},
"verbose": { "type": "boolean", "description": "Verbose mode for auto-paging debug output" }
}
}
],
@@ -453,6 +456,79 @@
},
"description": "Chart grid line options"
},
"catAxisItem": {
"type": "object",
"properties": {
"hidden": { "type": "boolean" },
"labelColor": { "type": "string" },
"labelFontBold": { "type": "boolean" },
"labelFontFace": { "type": "string" },
"labelFontItalic": { "type": "boolean" },
"labelFontSize": { "type": "number" },
"labelFrequency": { "type": "string" },
"labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
"labelRotate": { "type": "number" },
"lineColor": { "type": "string" },
"lineShow": { "type": "boolean" },
"lineSize": { "type": "number" },
"lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
"majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
"majorTimeUnit": { "type": "string" },
"majorUnit": { "type": "number" },
"maxVal": { "type": "number" },
"minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
"minorTimeUnit": { "type": "string" },
"minorUnit": { "type": "number" },
"minVal": { "type": "number" },
"multiLevelLabels": { "type": "boolean" },
"orientation": { "type": "string", "enum": ["minMax"] },
"title": { "type": "string" },
"titleColor": { "type": "string" },
"titleFontFace": { "type": "string" },
"titleFontSize": { "type": "number" },
"titleRotate": { "type": "number" },
"gridLine": { "$ref": "#/definitions/optsChartGridLine" },
"labelFormatCode": { "type": "string" },
"baseTimeUnit": { "type": "string" },
"crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] }
},
"description": "Category axis item for catAxes array"
},
"valAxisItem": {
"type": "object",
"properties": {
"crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
"displayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] },
"displayUnitLabel": { "type": "boolean" },
"hidden": { "type": "boolean" },
"labelColor": { "type": "string" },
"labelFontBold": { "type": "boolean" },
"labelFontFace": { "type": "string" },
"labelFontItalic": { "type": "boolean" },
"labelFontSize": { "type": "number" },
"labelFormatCode": { "type": "string" },
"labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] },
"labelRotate": { "type": "number" },
"lineColor": { "type": "string" },
"lineShow": { "type": "boolean" },
"lineSize": { "type": "number" },
"lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] },
"logScaleBase": { "type": "number", "minimum": 2, "maximum": 99 },
"majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
"majorUnit": { "type": "number" },
"maxVal": { "type": "number" },
"minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] },
"minVal": { "type": "number" },
"orientation": { "type": "string", "enum": ["minMax"] },
"title": { "type": "string" },
"titleColor": { "type": "string" },
"titleFontFace": { "type": "string" },
"titleFontSize": { "type": "number" },
"titleRotate": { "type": "number" },
"gridLine": { "$ref": "#/definitions/optsChartGridLine" }
},
"description": "Value axis item for valAxes array"
},
"chartOptions": {
"allOf": [
{ "$ref": "#/definitions/position" },
@@ -466,7 +542,7 @@
"chartColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } },
"chartColorsOpacity": { "type": "number" },
"dataBorder": { "$ref": "#/definitions/borderProps" },
"displayBlanksAs": { "type": "string" },
"displayBlanksAs": { "type": "string", "enum": ["gap", "span", "zero"] },
"invertedColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } },
"lang": { "type": "string" },
"layout": { "$ref": "#/definitions/position" },
@@ -500,7 +576,7 @@
}
},
"catAxes": { "type": "array", "items": { "type": "object" } },
"catAxes": { "type": "array", "items": { "$ref": "#/definitions/catAxisItem" } },
"catAxisBaseTimeUnit": { "type": "string" },
"catAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
"catAxisHidden": { "type": "boolean" },
@@ -551,7 +627,7 @@
"serAxisMajorUnit": { "type": "number" },
"serAxisMinorTimeUnit": { "type": "string" },
"serAxisMinorUnit": { "type": "number" },
"serAxisOrientation": { "type": "string" },
"serAxisOrientation": { "type": "string", "enum": ["minMax"] },
"serAxisTitle": { "type": "string" },
"serAxisTitleColor": { "type": "string" },
"serAxisTitleFontFace": { "type": "string" },
@@ -563,7 +639,7 @@
"secondaryValAxis": { "type": "boolean" },
"showValAxisTitle": { "type": "boolean" },
"valAxes": { "type": "array", "items": { "type": "object" } },
"valAxes": { "type": "array", "items": { "$ref": "#/definitions/valAxisItem" } },
"valAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] },
"valAxisDisplayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] },
"valAxisDisplayUnitLabel": { "type": "boolean" },
@@ -595,11 +671,11 @@
"valGridLine": { "$ref": "#/definitions/optsChartGridLine" },
"valLabelFormatCode": { "type": "string" },
"bar3DShape": { "type": "string" },
"barDir": { "type": "string" },
"bar3DShape": { "type": "string", "enum": ["box", "cylinder", "cone", "pyramid"] },
"barDir": { "type": "string", "enum": ["bar", "col"] },
"barGapDepthPct": { "type": "number" },
"barGapWidthPct": { "type": "number" },
"barGrouping": { "type": "string" },
"barGrouping": { "type": "string", "enum": ["clustered", "stacked", "standard"] },
"barOverlapPct": { "type": "number" },
"dataNoEffects": { "type": "boolean" },
@@ -625,7 +701,7 @@
"dataLabelFontItalic": { "type": "boolean" },
"dataLabelFontSize": { "type": "number" },
"dataLabelFormatCode": { "type": "string" },
"dataLabelFormatScatter": { "type": "string" },
"dataLabelFormatScatter": { "type": "string", "enum": ["custom", "customXY", "XY"] },
"dataLabelPosition": { "type": "string", "enum": ["b", "bestFit", "ctr", "l", "r", "t", "inEnd", "outEnd"] },
"dataTableFontSize": { "type": "number" },
@@ -642,7 +718,7 @@
"legendPos": { "type": "string", "enum": ["b", "l", "r", "t", "tr"] },
"title": { "type": "string" },
"titleAlign": { "type": "string" },
"titleAlign": { "type": "string", "enum": ["l", "ctr", "r"] },
"titleBold": { "type": "boolean" },
"titleColor": { "type": "string" },
"titleFontFace": { "type": "string" },
@@ -708,6 +784,7 @@
"background": { "$ref": "#/definitions/backgroundProps" },
"color": { "$ref": "#/definitions/hexColor", "description": "Default text color for this slide" },
"hidden": { "type": "boolean", "description": "Whether slide is hidden" },
"notes": { "type": "string", "description": "Speaker notes for this slide" },
"slideNumber": { "$ref": "#/definitions/slideNumberProps" },
"objects": {
"type": "array",
@@ -736,6 +813,16 @@
"company": { "type": "string" },
"revision": { "type": "string" },
"rtlMode": { "type": "boolean" },
"masterSlide": { "type": "object", "description": "Master slide reference object" },
"presLayout": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Layout name (e.g. LAYOUT_16x9)" },
"width": { "type": "number", "description": "Slide width in inches" },
"height": { "type": "number", "description": "Slide height in inches" }
},
"description": "Presentation layout dimensions"
},
"theme": {
"type": "object",
"properties": {