Repository navigation
chart: a title styling prop is rejected on a chart with no title element, while the axis path creates one #536
Description
Summary
A title.* styling property is rejected on a chart that has no title element yet, while the same key on the axis path creates the title and applies the style. The chart path and the axis path disagree about the same situation.
Measured in xlsx, docx and pptx, on the shipped 1.0.151 and on a 1.0.158 build of current main (06ab5201).
Reproduction
officecli create a.xlsx
officecli add a.xlsx /Sheet1 --type chart --prop 'data=Sales:10,20'
# chart path: the chart has no <c:title> element
officecli set a.xlsx '/Sheet1/chart[1]' --prop 'title.size=20'Error: No properties applied to /Sheet1/chart[1]
UNSUPPORTED props: title.size.
The axis path, same file, same starting condition (no axis title exists):
officecli set a.xlsx '/Sheet1/chart[1]/axis[@role=value]' --prop 'title.size=20'Updated /Sheet1/chart[1]/axis[@role=value]: title.size=20 (applied: title=, title.size=20pt)
and the part gains a title: <c:title> count 0 → 1, holding <a:defRPr sz="2000" b="1"/> with empty text.
Measured
xlsx, chart starting with zero <c:title> elements, counting them in xl/drawings/charts/chart1.xml before and after:
| path | command | exit code | <c:title> before → after |
|---|---|---|---|
| chart | set 'title.size=20' |
2 | 0 → 0 |
| chart | set 'title.bold=true' |
2 | 0 → 0 |
| chart | set 'title.color=FF0000' |
2 | 0 → 0 |
| chart | set 'title.font=Arial' |
2 | 0 → 0 |
| axis | set 'title.size=20' |
0 | 0 → 1 |
| chart, title present | set 'title.size=20' |
0 | 1 → 1, sz="2000" |
The same split holds for the other two formats:
| format | path used | chart with no title | after title=Sales |
|---|---|---|---|
| xlsx | /Sheet1/chart[1] |
rc 2 | rc 0 |
| docx | /chart[1] |
rc 2 | rc 0 |
| pptx | /slide[1]/chart[1] |
rc 2 | rc 0 |
Two things that are not affected, so the fix does not need to touch them:
title=Salesalone creates the title (rc 0).- A single call carrying text and styling works:
--prop 'title=Sales' --prop 'title.size=20'→ rc 0,sz="2000"written. Only the styling-only case is rejected.
Where it is
src/officecli/Core/Chart/ChartHelper.Setter.cs:301-310 — the fan-out for the styling keys:
case "title.font" or "titlefont":
case "title.size" or "titlesize":
case "title.color" or "titlecolor":
case "title.bold" or "titlebold":
case "title.glow" or "titleglow":
case "title.shadow" or "titleshadow":
case "title.lang" or "titlelang":
{
var ctitle = chart.GetFirstChild<C.Title>();
if (ctitle == null) { unsupported.Add(key); break; }The axis twin has already solved exactly this: src/officecli/Core/Chart/ChartHelper.Axis.cs:747-754 buffers the four keys into pendingAxisTitleStyling and applies them after the translated forward, and its comment names the failure verbatim — these keys "used to fall to default and forward to the chart-level title handler, which then mutated the wrong title (or returned UNSUPPORTED when no chart title existed)". The chart level is the site that was left behind.
One design question
The axis path resolves the situation by creating an empty title and styling it. The chart path could mirror that, which is what I would submit — or you may prefer a clear "set title= first" error over an empty title element that renders as nothing. Either way the two paths should agree; right now a caller cannot tell from the docs why the same key works on one and not the other.
I'd like to work on this.
Activity
argszero commentedon Oct 22, 2026
Fixed in #537.
Cause. The chart-level branch kept a
if (ctitle == null) { unsupported.Add(key); break; }guard for all seventitle.*styling pairs, while the axis branch had already solved the same situation inR52 bt-2(ChartHelper.Axis.cs) by building an empty<c:title>and applying the key to its runs. Sotitle.size=20on a chart without a title element was reported as an unknown prop (exit 2) while the same request one level down on an axis returned exit 0 — the asymmetry in the report.Fix. Mirror the axis precedent at chart level: when the chart has no title, synthesize the same empty-title shape the axis path builds, insert it as
c:titleat itsCT_Chartposition (first child, the sitetitle=already uses), then run the existing per-run styling code unchanged. The text stays empty — the prop describes how a title looks, not what it says — andautoTitleDeletedis left untouched, so a title the caller suppressed does not reappear blank. This is the direction I described when claiming the issue; if you would rather the styling props keep reportingUNSUPPORTEDon a title-less chart, say so and the PR is easy to drop.Verification (two-arm, both binaries from the same clone, base
06ab5201):title.size/title.bold/title.color/title.fontflip from exit 2 to exit 0;title=, text-plus-style in one call, an already-titled chart and the axis path itself are byte-for-byte unchanged;dump→batchround trip keeps the created title and validates clean. Same flip for docx and pptx, which share this setter.examples/each hold a reachable chart with no<c:title>—budget_review_v2.pptx(slide 5 chart 1) goes 1 → 2 titles, exit 2 → 0, empty title withsz="2000", idempotent on a second call;charts-area.pptx(slide 2 chart 4,autoTitleDeleted val="1") goes 33 → 34 titles with the suppression flag and its value intact.<c:title>counts equal (459/459),dumpoutput byte-identical,validateexit 0 throughout.goworm commentedon Oct 26, 2026
Confirmed. We went the other way from "mirror the axis": neither path fabricates a title any more. A title.* styling key on a chart or axis that has no title is now rejected as
invalid_valuewith a message (and a structuredsuggestion) saying to passtitle=<text>in the same command, which keeps working as before. The axis path's empty-title creation is removed, since an empty<c:title>renders as nothing. Presets still skip title styling on a title-less chart. Will be in the next release; #537 is not merged.