HotXLS Release Notes
Version history for HotXLS user-visible features, fixes, XLS/XLSX enhancements, export updates, compatibility changes, and documentation changes.
[Unreleased]
Version 2.88.111
- Classic XLS worksheets now support standalone drawing text boxes. Use
TXLSShapes.AddTextBoxto create aTXLSTextBoxwith its own row/column anchor, text, text runs, line/fill formatting, and normal shape deletion without creating a cell comment.
Version 2.88.110
- ApiTour demos now cover more workbook automation patterns. The Delphi and C++Builder ApiTour samples demonstrate callback-based range writing with
WriteCells, loaded-cell scanning withForEachCell, template updates withFindTextandReplaceText, selected-range HTML export toApiTour-Range.html, and Delphi custom formula callbacks throughOnUserFunction.
Version 2.88.109
- QuickStart demos now show a fuller first workbook. The Delphi and C++Builder QuickStart samples create a styled multiplication table with row and column total formulas, frozen panes, and matching XLS/XLSX actions; the demo tree now also includes polished HTML readme pages for each example.
Version 2.88.108
- XLSX ranges now have direct HTML export helpers.
TXLSXRange.SaveAsHTML(FileName)andTXLSXRange.SaveAsHTML(Stream)export only the selected XLSX range as a UTF-8 HTML table, sharing the workbook HTML renderer's escaping, basic style output, and merged-cell span handling.
Version 2.88.107
- XLSX worksheets now include text search and replacement helpers.
TXLSXWorksheet.FindTextandTXLSXWorksheet.ReplaceTextscan loaded text cells in row-major order, skip formulas and non-text values, and support optional case-sensitive matching for template workflows.
Version 2.88.106
- BIFF8 Window2 view flags now preserve custom gridline and frozen-pane state. HotXLS now clears
fDefaultHdrwhenWindow2.icvHdrstores a custom gridline color, preserves Excel-authoredfFrozenNoSplitduring open/save, and writesfFrozenNoSplitforFreezePanesoutput.
Version 2.88.105
- CF12 formula thresholds now round-trip through BIFF8 Data Bar, Color Scale, and Icon Set rules. Formula-based threshold values compile to CF12 formula token bytes on save and are restored to
TXLSCfValue.Valuewhen a workbook is reopened.
Version 2.88.104
- Classic XLS formulas and icon-set thresholds now emit tighter BIFF8 records. Integer constants in formulas use the compact
tInttoken when they fit the BIFF unsigned-integer range, andTXLSIconSetSpec.ThresholdEqualsInclude[Index]now controls the CF12 icon-set threshold equals byte so callers can choose include or exclude comparison semantics.
Version 2.88.103
- Classic XLS worksheets now include a text find helper.
IXLSWorksheet.FindText(SearchText, Row, Col[, MatchCase])locates the first matching loaded text cell, skips formulas and non-text cells, and clears the returned coordinates when no match is found.
Version 2.88.102
- Classic XLS worksheets now include a text replacement helper.
IXLSWorksheet.ReplaceText(SearchText, ReplacementText[, MatchCase])replaces matches in loaded text cells, skips formulas and non-text cells, and reports the changed cell count for template-filling workflows.
Version 2.88.101
- Ranges now have direct HTML export helpers.
IXLSRange.SaveAsHTML(FileName)andIXLSRange.SaveAsHTML(Stream)export the selected range through the existing HTML renderer, so callers no longer need to create aTXLSHTMLExportinstance or passxlHTMLexplicitly for common range fragments.
Version 2.88.100
TXLSWorkbook.GetSheetNames(Stream, AList)now reads XLSX package streams as well as classic XLS streams. Applications can pass memory streams, database BLOB streams, or downloaded XLSX streams to the classic facade and receive ordered worksheet names without first saving to a file.
Version 2.88.99
- Worksheet lookup, hyperlink creation, and multi-area selection APIs are now easier to use. Classic XLS and XLSX sheet collections add
TryGetSheetByName, classic XLS worksheets addAddHyperlinkconvenience overloads, andIXLSWorksheet.SelectAreascan write active multi-range selections.
Version 2.88.98
- Classic XLS workbooks now expose a
UserNamealias for the saved-by field.IXLSWorkbook.UserNamemaps to the same WRITEACCESS value asLastSavedBy, so code that uses theUserNamenaming convention can read and update the workbook audit name directly.
Version 2.88.97
- XLSX worksheets now share the callback cell scan and range write helpers.
TXLSXWorksheet.ForEachCellenumerates loaded cells in row order with value and formula text, whileTXLSXWorksheet.WriteCellsfills numeric or A1-style ranges from a callback with per-cell skip and cancellation.
Version 2.88.96
- Classic XLS worksheets now support callback-driven range writing.
IXLSWorksheet.WriteCellsfills a 1-based numeric range or A1-style range in row order, lets the callback provide each cell value, supports per-cell skip, and can cancel long write loops early.
Version 2.88.95
- Classic XLS worksheets can now enumerate loaded cells through a callback.
IXLSWorksheet.ForEachCellwalks existing worksheet cells in row order, passes 1-based sheet, row, and column coordinates with value and formula text, and lets the callback cancel long scans early.
Version 2.88.94
- Formula calculation can now call application-defined functions.
TXLSWorkbook.OnUserFunctionandTXLSXWorkbook.OnUserFunctionletCalculatedelegate custom or unsupported worksheet functions to application code, passing evaluated arguments and using the returnedVariantresult when handled.
Version 2.88.93
- Classic XLS worksheet protection now exposes per-action permission flags.
IXLSWorksheetaddsAllowDeleteRows,AllowInsertRows,AllowFormatCells,AllowSort,AllowAutoFilter, and the remaining SheetProtect permission properties, with BIFF8 options written and read back through the existing protection records.
Version 2.88.92
- Classic XLS VBA payloads now have explicit load/save/clear helpers.
IXLSWorkbook.LoadVBAProjectFromFile,SaveVBAProjectToFile,HasVBAProject, andClearVBAProjectexpose the preserved OLE VBA storage payload whileVBAProjectremains the parsed read-only module view.
Version 2.88.91
- Classic XLS AutoFilter criteria can now be inspected through a field collection.
IXLSWorksheet.AutoFilterColumnsexposes the current BIFF8 AutoFilter field count and 1-based field items, including active state, operator, and storedCriteria1/Criteria2DOPER values.
Version 2.88.90
- Classic XLS worksheets now have one-call AutoFilter criteria setup.
IXLSWorksheet.ApplyAutoFiltersets a worksheet AutoFilter range and writes a field criterion in the same call, with A1-style and 1-based numeric range overloads plus optional two-criterion operators.
Version 2.88.89
- Classic XLS shapes can now be removed by cell range.
TXLSShapes.DeleteInRangedeletes anchored pictures or shapes whose OfficeArt client anchors intersect a 1-based worksheet range, returning the number of shapes removed.
Version 2.88.88
- Classic HTML export table layout attributes are now configurable.
TXLSHTMLExport.TableBorderWidth,CellPadding, andCellSpacingcontrol the generated<table>attributes for both styled and simple HTML output while defaulting to the previous zero values.
Version 2.88.87
- Classic HTML export now supports minimal table output.
TXLSHTMLExport.SimpleExportwrites a plain HTML table without generated CSS classes, hyperlink anchors, or comment title attributes while preserving escaped display text and merged-cell spans.
Version 2.88.86
- Classic HTML export can now write a document title.
TXLSHTMLExport.Titlewrites an escaped HTML<title>element before the generated style block, while the default empty value keeps existing output unchanged.
Version 2.88.85
- Classic HTML export now carries cell comments as tooltips.
TXLSHTMLExport.Optionsnow includesxhCommentsby default, writing cell comment text as escapedtitleattributes while still allowing callers to remove the option for plain cell output.
Version 2.88.84
- Classic HTML export hyperlink output is now configurable.
TXLSHTMLExport.Optionsdefaults to[xhHyperlinks]for existing behavior, and callers can removexhHyperlinksto export linked cells as plain escaped text.
Version 2.88.83
- Classic HTML export now preserves cell hyperlinks.
SaveAsHTMLwraps linked cells in HTML anchor tags, emits hyperlink addresses as escapedhrefvalues, carries ScreenTip text as escapedtitleattributes, and keeps the displayed cell text escaped inside the link.
Version 2.88.82
- Classic XLS workbooks can now control the saved-by user name.
IXLSWorkbook.LastSavedByexposes the WRITEACCESS saved-by field so generated or edited workbooks can store an explicit audit name while unedited source workbooks continue preserving their original metadata.
Version 2.88.81
- Classic XLS worksheets can now control the displayed gridline color index.
IXLSWorksheet.GridlineColorIndexexposes the Window2icvHdrfield so saved worksheets can reopen with a chosen gridline color while leaving gridline visibility and print gridline settings unchanged.
Version 2.88.80
- Classic XLS workbooks can now set the active sheet by index.
IXLSWorkbook.ActiveSheetIndexexposes a 1-based active-tab property backed by the WINDOW1 active sheet field, allowing saved workbooks to reopen on a chosen worksheet without changing the first visible tab.
Version 2.88.79
- Classic XLS worksheet outlines can now control automatic outline styles.
TXLSOutline.ApplyStylesexposes the WSBool outline style flag, allowing saved worksheets to preserve or explicitly enable Excel outline style application without changing outline levels or summary placement.
Version 2.88.78
- Classic XLS page setup can now control automatic page breaks.
IXLSPageSetup.AutoPageBreaksexposes the worksheet WSBool automatic page-break flag, allowing saved worksheets to keep automatic page breaks enabled or disable them explicitly while preserving manual page breaks.
Version 2.88.77
- Classic XLS workbooks can now control AutoFilter date grouping.
IXLSWorkbook.AutoFilterDateGroupingexposes the Window1 date-grouping flag with a positive Boolean API, allowing saved workbooks to keep Excel's date filter grouping enabled or disable it explicitly.
Version 2.88.76
- Classic XLS workbooks can now set workbook window position and size directly.
IXLSWorkbook.XWindow,YWindow,WindowWidth, andWindowHeightexpose the Window1 geometry fields so saved workbooks can reopen with a chosen window rectangle.
Version 2.88.75
- Classic XLS workbooks can now save the workbook window hidden state directly.
IXLSWorkbook.WindowHiddenmaps to the Window1 hidden flag, allowing saved workbooks to reopen with the workbook window hidden without changing worksheet visibility.
Version 2.88.74
- Classic XLS workbooks can now save the workbook window minimized state directly.
IXLSWorkbook.Minimizedmaps to the Window1 minimized flag, allowing saved workbooks to reopen with the workbook window minimized.
Version 2.88.73
- Classic XLS workbooks can now hide or show the vertical scrollbar directly.
IXLSWorkbook.ShowVerticalScrollmaps to the Window1fDspVScrollflag, allowing saved workbooks to open with the vertical scrollbar hidden or visible.
Version 2.88.72
- Classic XLS workbooks can now hide or show the horizontal scrollbar directly.
IXLSWorkbook.ShowHorizontalScrollmaps to the Window1fDspHScrollflag, allowing saved workbooks to open with the horizontal scrollbar hidden or visible.
Version 2.88.71
- Classic XLS workbooks can now hide or show the sheet tab bar directly.
IXLSWorkbook.ShowSheetTabsmaps to the Window1fDspTabsflag, letting saved workbooks open with sheet tabs hidden or visible without changing worksheet visibility.
Version 2.88.70
- Classic XLS workbooks can now set the workbook tab bar ratio directly.
IXLSWorkbook.TabRatioexposes the Window1wTabRatiovalue so callers can adjust the space reserved for sheet tabs versus the horizontal scrollbar.
Version 2.88.69
- Classic XLS workbooks can now set the first visible sheet tab directly.
IXLSWorkbook.FirstSheetexposes the Window1itabFirstvalue as a 1-based property, so callers can choose which sheet tab appears at the left edge when Excel opens the workbook.
Version 2.88.68
- Classic XLS worksheets can now preserve and set outline symbol visibility.
IXLSWorksheet.DisplayOutlineSymbolsmaps to the BIFF WINDOW2fDspGutsflag, allowing saved sheets to hide outline and grouping symbols in the window view.
Version 2.88.67
- Classic XLS worksheets can now preserve and set row and column heading visibility.
IXLSWorksheet.DisplayHeadingsmaps to the BIFF WINDOW2fDspRwColflag, allowing saved sheets to hide row numbers and column letters in the window view.
Version 2.88.66
- Classic XLS worksheets can now preserve and set the show-formulas view.
IXLSWorksheet.DisplayFormulasmaps to the BIFF WINDOW2fDspFmlaflag so Excel can open a sheet showing formula text instead of calculated results.
Version 2.88.65
- Classic XLS worksheets can now set Normal view zoom independently.
IXLSWorksheet.NormalViewZoomexposes the cached WINDOW2 Normal zoom so callers can save distinct zoom levels while a sheet is in Page Break Preview.
Version 2.88.64
- Classic XLS worksheets can now set Page Break Preview zoom directly.
IXLSWorksheet.PageBreakPreviewZoomexposes the cached WINDOW2 preview zoom so callers can save different Normal and Page Break Preview zoom levels.
Version 2.88.63
- Classic XLS worksheets now expose split-pane helpers.
IXLSWorksheet.SplitPanes(Width, Height)creates a movable pane split using the existing BIFF pane writer, andUnsplitPanesclears the pane record before saving.
Version 2.88.62
- Classic XLS worksheets now have simpler frozen-pane helpers.
IXLSWorksheet.FreezePanes(Cols, Rows)freezes the requested left columns and top rows, whileUnfreezePanesremoves the pane record before saving.
Version 2.88.61
- Classic XLS sheet-name discovery now accepts streams.
TXLSWorkbook.GetSheetNames(Stream, List)reads BIFF sheet names from a caller-provided XLS stream without loading worksheet contents, matching the file-name helper for in-memory workbook data.
Version 2.88.60
- Worksheet collections now expose direct by-name lookup helpers.
IXLSWorkSheets.SheetByName(Name)andTXLSXSheets.SheetByName(Name)return the matching worksheet object ornil, so callers no longer need to resolve an index before accessing a sheet.
Version 2.88.59
- The XLSX facade now reads sheet names directly from streams.
TXLSXWorkbook.GetSheetNames(Stream, List)reads ordered names fromxl/workbook.xmlwithout loading worksheet XML, matching the file-name overload for in-memory and BLOB-backed workbooks.
Version 2.88.58
- The XLSX facade now exposes quick sheet-name discovery.
TXLSXWorkbook.GetSheetNames(FileName, List)reads ordered names fromxl/workbook.xmlwithout loading worksheet XML.
Version 2.88.57
TXLSWorkbook.GetSheetNamesnow supports XLSX packages. The method readsxl/workbook.xmlfrom.xlsx,.xlsm,.xltx, and.xltmfiles and returns ordered sheet names without loading worksheet XML.
Version 2.88.56
- Classic XLS workbooks can now read sheet names without loading worksheets.
TXLSWorkbook.GetSheetNames(FileName, List)scans the workbook globals stream and returns ordered sheet names from Excel 97-2003.xlsfiles.
Version 2.88.55
- Classic XLS range AutoFilter criteria now write BIFF8 filter records.
TXLSRange.Autofilter(Field, Criteria)creates the filter range when needed and emitsAUTOFILTERconditions for simple string, numeric, Boolean, blank, nonblank, and comparison-prefixed criteria.
Version 2.88.54
- Classic XLS worksheets now have worksheet-level AutoFilter helpers.
IXLSWorksheet.SetAutoFilter,ClearAutoFilter, andAutoFilterRangemanage BIFF8 filter dropdown ranges directly while reusing the existingAUTOFILTERINFOand filter database name writer.
Version 2.88.53
- Classic XLS worksheets now expose list data-validation creation.
IXLSWorksheet.AddListValidation(Range, Items)writes BIFF8DVALandDVrecords, keeps the dropdown visible by default, supports comma-separated inline lists, and preserves the rules throughOpen/SaveAsround-trips.
Version 2.88.52
- Formula calculation now works consistently across XLS and XLSX entry points.
TXLSWorkbook.Calculatenow accepts Excel-style leading-equals formulas such as=SUM(A1:B1)+C1, and the XLSX facade addsTXLSXWorkbook.CalculateplusTXLSXWorksheet.Calculate. The XLSX path shares the HotXLS formula token parser and evaluator for cell and range references, referenced formula cells, cross-sheet references, and workbook or sheet scoped defined-name ranges.
Version 2.88.51
- XLSX ranges now support Excel-style merge across.
TXLSXRange.Merge(True)writes one<mergeCell>entry per row, matching the classic XLSRange.Merge(True)behavior, while no-argumentMergeandMerge(False)continue to create one full-range merged rectangle.
Version 2.88.50
- BIFF8 HeaderFooter and HFPicture placement now match the current MS-XLS rules. HotXLS preserves Excel-authored
HeaderFooter($089C) records for even / first page header and footer text, and replaysHFPicture($0866) records after worksheet dimensions and drawing objects per the worksheet ABNF, so page-layout details survive .xls open/save round-trips.
Version 2.88.49
- Comment rich-text runs now have a typed API.
TXLSComment.TextRunsexposes BIFF8TXOFormatRunentries so callers can inspect or set per-character font boundaries in .xls comments while HotXLS still preserves the underlying run bytes for safe round-trips.
Version 2.88.48
- Pivot-table supplemental SX records are now exposed through the typed API. HotXLS now attaches loaded BIFF8 pivot records such as
SXIVD,SXLI,SXFormat,SXEx,SXVDEx,SXRule,SXFilt, andSXAddltoTXLSPivotTable.SupplementalRecordsfor inspection while still preserving the original raw bytes for save round-trips.
Version 2.88.47
- External workbook cached values now survive .xls open/save round-trips. HotXLS now preserves BIFF8
XCTandCRNrecords attached to loadedSUPBOOKentries, so cached cell values for external workbook links are no longer dropped during save.
Version 2.88.46
- Built-in style outline levels now survive .xls open/save round-trips. HotXLS now reads and preserves the BIFF8
STYLEiLevelbyte for built-in styles, so Excel RowLevel_1..7 and ColLevel_1..7 style metadata is no longer reset to the default during save.
Version 2.88.45
- Defined name metadata now survives .xls open/save round-trips. HotXLS now preserves loaded BIFF8
NAMErecord option bits and shortcut bytes unless the defined name is edited, so Excel-authored function, object, procedure, calculated-expression, and binary-name metadata is no longer cleared during save.
Version 2.88.44
- ObProj workbook markers now survive .xls open/save round-trips. HotXLS now preserves loaded BIFF8
ObProjrecords even when no VBA storage is exposed through the object model, so workbook-level VBA project markers and third-party metadata are no longer dropped during save.
Version 2.88.43
- Worksheet option flags now survive .xls open/save round-trips. HotXLS now preserves unmodeled BIFF8
WSBOOLbits such as dialog-sheet, automatic page-break, style-application, and outline-gutter display flags while still updating the existing fit-to-page and outline summary settings through the public API.
Version 2.88.42
- Additional workbook metadata records now survive .xls open/save round-trips. HotXLS now preserves loaded BIFF8
COUNTRY,BOOKBOOL,BACKUP,FNGROUPCOUNT,USESELFS, andRECALCIDrecord bodies instead of rewriting them with defaults, keeping workbook-level compatibility and calculation metadata stable.
Version 2.88.41
- Multi-area selections and active pane focus now survive .xls open/save round-trips. HotXLS now preserves loaded BIFF8
SELECTIONrecords and thePANESactive-pane value unless the sheet selection or panes are changed through the API, so Excel split/frozen pane focus and multi-range selections are no longer collapsed during save.
Version 2.88.40
- External workbook names now survive .xls open/save round-trips. HotXLS now preserves loaded BIFF8
EXTERNALNAMErecords attached to externalSUPBOOKentries, including DDE/OLE/link metadata and formula tails, so external names and link definitions are no longer dropped when saving.
Version 2.88.39
- Password-protected BIFF8 .xls files now use and read RC4 CryptoAPI FILEPASS records. HotXLS now writes Excel 2007/2010-style
FILEPASSvMajor=2 metadata for encrypted .xls workbooks and reads both legacy RC4 vMajor=1 and RC4 CryptoAPI vMajor=2/3/4 files, so password-protected workbooks no longer fail or downgrade to the older encryption header.
Version 2.88.38
- Excel-authored .xls hyperlink metadata now survives open/save round-trips. HotXLS now preserves the BIFF8
HLinkrecord body for loaded hyperlinks unless the hyperlink is edited, keeping sourcestreamVersion,hlstmfoption bits such asfIcon, moniker data, and related target metadata stable across saves.
Version 2.88.37
- Workbook window state now survives .xls open/save round-trips. HotXLS now reads and preserves the BIFF8
WINDOW1record's window position, size, visibility/iconic flags, first displayed sheet tab, selected-tab count, and tab-scrollbar ratio instead of rewriting them with defaults on every save.
Version 2.88.36
- BIFF8 WriteAccess saved-by users now survive .xls open/save round-trips. HotXLS now reads and preserves the
$005C WRITEACCESSrecord body instead of replacing Excel's saved-by user withHotXLS, so workbook audit metadata remains stable after saving.
Version 2.88.35
- Excel comment authors now survive .xls open/save round-trips. The BIFF8
Noterecord'sstAuthorfield is now parsed and passed into the drawing note model instead of being replaced with an empty string, so the comment "Created by" author written by Excel is preserved when HotXLS saves the workbook again.
Version 2.88.34
- Shape OfficeArt FOPT options are now exposed through a low-level option bag.
TXLSShape.OfficeArtOptionslets advanced callers inspect, set, and delete any stored FOPT PID as aLongWordorWideString, while the existing high-levelLine,Fill, color, and visibility APIs remain unchanged.
Version 2.88.33
- Additional OfficeArt solver-container rule records now round-trip. Beyond typed
msofbtConnectorRule($F012), HotXLS now preserves non-folder solver children such asOfficeArtFArcRule($F014) andOfficeArtFCalloutRule($F017) as raw OfficeArt records insidemsofbtSolverContainer($F005), so advanced Excel drawings no longer lose non-connector solver relationships during save.
Version 2.88.32
- OfficeArt ClientData markers are no longer duplicated after unknown-record preservation. MS-XLS defines
OfficeArtClientData($F011) as a zero-length marker whose followingObjrecord remains a separate BIFF record. v2.88.32 recognizes that marker instead of capturing it as an unknown SpContainer leaf, so parsed shapes emit exactly one $F011 marker during save while malformed container-style assumptions remain out of the XLS path.
Version 2.88.31
- OfficeArt connector rules now round-trip. Excel-authored .xls drawings store connector-line bindings in
msofbtSolverContainer($F005) with childmsofbtConnectorRule($F012) records. v2.88.31 parses and emits that $F005/$F012 pair throughTMsoConnectorRules, preserving the start shape, end shape, connector shape, and connection sites across save/reload. The earlier audit note listed $F121, but MS-ODRAW definesOfficeArtFConnectorRuleas $F012.
Version 2.88.30
- Unrecognized OfficeArt SpContainer leaf records now round-trip. Excel-authored .xls files commonly embed leaf Fbts such as
msofbtClientTextbox($F00D),OfficeArtFPSPL($F11D), andOfficeArtUDefProp($F122) inside each shape SpContainer. v2.88.30 captures unknown shape-level leaves asTMsoFbtUnknownand re-emits them in parse order between the shapeClientAnchorand the trailing zero-bodymsofbtClientDatamarker. - Scope and follow-up. Other drawing-level unknown leaves remain deferred; the connector-rule solver-container case is handled by v2.88.31. Container-form unknowns, such as
msofbtClientData($F011) used as a container instead of a sibling marker, remain audit-round Slice 3.
Version 2.88.29
- ComboBox and ListBox OBJ unknown subrecords now round-trip. TMSOShapeComboBox.ParseObj and TMSOShapeListBox.ParseObj already typed-parsed FtSbs ($000C), FtSbsFmla ($000E), and FtLbsData ($0013) into dedicated fields, but any other source-file subrecord (FtMacro $0004, ObjLinkFmla, FtNts $000D, etc.) appearing between CMO and FtLbsData was silently dropped at case fall-through. v2.88.29 adds the same else-branch as v2.88.28 Picture, capturing the unknown subrecord's 4-byte header + body into
FObjTailRaw. Both AddObj methods emit the captured bytes between the CMO body and the FtSbs subrecord to match the BIFF8 spec's subrecord ordering. - FtLbsData's swallow-all behavior limits the scope. The existing
$0013case overridesRecLen := Len - offsto absorb all bytes after the FtLbsData header intoFObjRec_LBSData's blob, so subrecords appearing AFTER FtLbsData (typically just FtEnd) are still round-tripped through that mechanism unchanged. The new FObjTailRaw integration specifically targets the gap before FtLbsData where FtMacro and ObjLinkFmla typically live. Freshly authored combo / list boxes via the Drawing API have emptyFObjTailRaw→ byte-identical output to v2.88.28.
Version 2.88.28
- Picture-shape OBJ unknown subrecords now round-trip. TMSOShapePicture.ParseObj already typed-parsed FtCf ($0007), FtPioGrbit ($0008), and FtPictFmla ($0009) into dedicated fields, but any other source-file subrecord (FtMacro $0004, FtNts $000D, etc.) was silently dropped at the case fall-through. v2.88.28 adds an else-branch that appends the full 4-byte header + body of unrecognized subrecords into
FObjTailRaw; TMSOShapePicture.AddObj then emits those bytes verbatim after its typed subrecord output and before the FtEnd marker. Source-file picture objects with macro hooks survive the resave. - typed + raw coexistence pattern. Unlike PictureFrame (v2.88.27) which used an if/else swap between typed and raw emit, Picture combines both: the typed fields preserve the API surface (callers can still adjust
ftCfValueetc.), while the raw tail preserves only the unknown bytes. Newly authored pictures via the Drawing API have emptyFObjTailRaw, so output is byte-identical to v2.88.27.
Version 2.88.27
- OBJ trailing-subrecord round-trip extended to PictureFrame shapes. TMSOShapePictureFrame.AddObj previously emitted only the legacy hardcoded FtCf ($0007, body $FFFF) and FtPioGrbit ($0008, body $0000) subrecords between CMO and FtEnd. For freshly authored picture frames the new code still emits those defaults; for picture frames parsed from a source .xls the captured
FObjTailRawbytes are emitted verbatim instead, preserving any source-file FtPictFmla / non-default FtCf / non-default FtPioGrbit content across the save cycle. - Investigation note — Flush auto-corrects sz.
TMsoShapeContainer.Flushrewrites the Obj record's size header at commit time viaData.SetWord(Data.DataLength - 4, 2), which means the hardcodedsz := $1Avalues that have lived in HotXLS AddObj implementations for years were not spec bugs after all — Flush corrects them automatically. v2.88.27 still computes sz accurately for source-code clarity, but the runtime behavior is unchanged from that perspective.
Version 2.88.26
- OBJ trailing-subrecord round-trip extended to Chart and CheckBox shapes. v2.88.25 introduced raw-byte preservation for the bytes between an Obj record's CMO ($0015) and its FtEnd ($0000) marker, but only TMSOShapeTextBox.AddObj emitted the captured tail. This release applies the same pattern to TMSOShapeChart.AddObj and TMSOShapeCheckBox.AddObj — both share the simple "CMO + FtEnd" emit structure of TextBox, so the change is a one-line variable substitution (
sz := $1A + Word(Length(FObjTailRaw))) plus a verbatim byte loop before the FtEnd marker. - Chart trailing subrecords (e.g. FtMacro for embedded chart object macros) and checkbox state subrecords (FtCbls + FtCblsData + ObjLinkFmla, MS-XLS §2.5.140-141 / §2.5.149) now survive HotXLS save/reload. Freshly authored charts and checkboxes via the Drawing API still emit a default 26-byte CMO+FtEnd (byte-identical to v2.88.25), so existing callers see no behavior change. Picture / ComboBox / ListBox / PictureFrame shapes have their own dynamic typed-subrecord emit that conflicts with FObjTailRaw — their integration is deferred to future commits.
Version 2.88.25
- OBJ record trailing subrecords now preserved across the save cycle for comments. The BIFF8 Obj record ($005D) chains a sequence of typed subrecords after the mandatory CMO ($0015), terminated by an FtEnd ($0000) marker, per MS-XLS §2.4.181. For Note (comment) shapes the trailing block is typically a 26-byte FtNts (§2.5.149) carrying a GUID and the fSharedNote flag — both essential when Excel later merges comments across users in shared workbooks. HotXLS previously discarded every byte after CMO on read and emitted only an empty FtEnd on write, so any source-file FtNts was lost on every HotXLS resave.
- Generic-base capture, TextBox-only emit (R5-3 first slice). TMsoShapeContainer.ParseObj now captures the byte range between CMO body end and FtEnd marker into a new internal
FObjTailRaw: TBytesfield; TMSOShapeTextBox.AddObj inserts those bytes between its CMO emit and the FtEnd marker, with the Obj record header size adjusted accordingly. Newly authored comments (no source bytes) keep byte-identical output to v2.88.24. Other shape classes (Picture / CheckBox / ComboBox / ListBox / Chart) inherit the capture but their typed AddObj overrides keep the legacy "synthesise from scratch" behavior — raw-bytes emit for those shape types is deferred to future commits.
Version 2.88.24
- Multi-font and multi-color cell comments now round-trip. The BIFF8 TXO record's formatting-runs block (
rgTxoRuns+TxOLastRunper MS-XLS §2.5.272) carries the per-run font and color overrides that give a comment its rich-text styling. HotXLS previously discarded the entire block on read and emitted a single empty default run + LastRun on write — flattening any "Important!" highlighted in red, any bold word inside the comment text, into uniform default formatting on every HotXLS resave. - Raw-byte preservation in TMSOShapeTextBox. ParseTXO captures the post-text Continue bytes verbatim into a new internal
FFormatRunsRaw: TBytesfield; Store emits them back as the formatting-runs Continue and adjusts the base record'scbRunsheader to match. Newly authored comments (viaAddComment) keep the legacy single-default-run output unchanged, so byte-identical output is preserved for default workbooks. Typed Excel-API access to individual format runs is still deferred — the existing single-WideString comment API is unchanged.
Version 2.88.23
- Print resolution and copy count now round-trip — Setup record now fully covered. Three Setup-record Word fields (
iResat offset 12,iVResat offset 14,iCopiesat offset 32, per MS-XLS §2.4.257) carry Excel's "Page Setup → Page → Print quality" and "Number of copies" choices. HotXLS previously dropped all three on read and emitted hardcoded values (600 DPI / 600 DPI / 1 copy), so any user-tweaked resolution or copy count silently reverted on every HotXLS resave. - Three new TXLSPageSetup properties.
PrintResolution: LongWord,PrintVResolution: LongWord, andCopies: LongWord— all default to the values the writer previously hardcoded (600 / 600 / 1) so workbooks that never touch them produce byte-identical output to v2.88.22. Setters clamp atHigh(Word)so an over-large caller value cannot wrap on emit. - Setup record round-trip work complete. With this fix every user-visible field of the BIFF8 Setup record ($00A1) round-trips end to end — paper size / scale / first page number / fit-to-page width and height / orientation / print order / black-and-white / draft / print comments / cell errors / resolution / copies. Only the two spec-hint grbit bits (
fNoPls,fNoOrient) remain unexposed, both of which HotXLS writes as zero matching Excel's default behavior.
Version 2.88.22
- Custom Page Setup "First page number" now round-trips. The BIFF8 Setup record carries the workbook's Page Setup → Page → "First page number" choice as two related fields: grbit bit 7 (
fUsePage) selects between Excel's "Auto" default and an explicit starting page number, and the signed Word at offset 4 (iPageStart) holds that custom value, per MS-XLS §2.4.257. HotXLS previously dropped both on read and emittediPageStart = 1with the bit clear, so a user-typed "First page number: 100" silently snapped back to "Auto" on every HotXLS resave. - Two new TXLSPageSetup properties.
UseFirstPageNumber: Booleanmirrors the bit (False = "Auto", default);FirstPageNumber: Integercarries the iPageStart value (default 1, clamped to the spec's signed-Word range -32768..32767 by the setter). HotXLS round-tripsiPageStartverbatim even whenUseFirstPageNumberis False, so toggling between Auto and custom in Excel preserves the last user-typed value.
Version 2.88.21
- "Print comments at end of sheet" choice now survives the save cycle. The BIFF8 Setup record's grbit bit 9 (
fEndNotesper MS-XLS §2.4.257) carries the Page Setup → Sheet → Comments dropdown's distinction between "As displayed on sheet" (the default) and "At end of sheet". HotXLS already round-tripped PrintNotes (bit 5) to control whether comments print, but bit 9 was dropped on read and emitted as zero on write — so a user-chosen "At end of sheet" silently reverted to "As displayed on sheet" on every HotXLS resave. - New TXLSPageSetup.PrintNotesAtEnd Boolean property. Defaults to
Falsematching Excel's UI default. The reader and writer round-trip the bit verbatim (even whenPrintNotes = False), so a workbook with comments temporarily disabled retains the "at end of sheet" preference for when the user re-enables them — mirroring Excel's own behavior.
Version 2.88.20
- Workbook "Set precision as displayed" choice now round-trips. The BIFF8 CalcPrecision record ($000E) carries the workbook-wide File → Options → Advanced → "Set precision as displayed" checkbox per MS-XLS §2.4.35. HotXLS previously had no dispatcher case for $000E and TXLSWorkbook.StorePrecision emitted a hardcoded
1(= full precision retained). A user-checked "Set precision as displayed" silently reverted to "full precision" on every HotXLS resave — hiding the user's deliberate intent that any subsequent recalc should permanently truncate cell values to their formatted decimal places. - New TXLSWorkbook.UseFullPrecision Boolean property. Defaults to
True(matches the BIFF8 default and Excel's UI default of unchecked). Setting it toFalsemirrors checking the destructive "precision as displayed" option — HotXLS itself does not truncate any cell value on the round-trip, but the flag is now faithfully written back so Excel's next recalc respects it. With this fix, every BIFF8 calc record (CalcCount / CalcMode / CalcPrecision / CalcDelta / CalcIter / CalcSaveRecalc) round-trips end to end.
Version 2.88.19
- "Recalculate workbook before saving" checkbox now round-trips. The BIFF8 CalcSaveRecalc record ($005F) carries the workbook's File → Options → Save → "Recalculate workbook before saving" boolean, per MS-XLS §2.4.37. HotXLS previously had no dispatcher case for $005F and StoreCalculationSettings emitted a hardcoded
1— so a user-disabled "recalc on save" silently re-enabled itself on every HotXLS resave, which actually mattered for manual-calc workbooks where the user had deliberately frozen the on-disk cell values. - New TXLSWorkbook.RecalcOnSave Boolean property. Defaults to
True(matches both Excel's UI default and the legacy hardcoded value), so workbooks that never touch the property keep byte-identical output to v2.88.18. Setter accepts any Boolean; the reader normalizes arbitrary non-zero source bytes toTrue.
Version 2.88.18
- Iterative calculation settings (enable + max iterations + max change) now round-trip. The BIFF8 CalcIter ($0011), CalcCount ($000C), and CalcDelta ($0010) records together carry Excel's "Enable iterative calculation" trio of options (File → Options → Formulas), per MS-XLS §2.4.33 / §2.4.31 / §2.4.32. None of the three had a dispatcher case before, and StoreCalculationSettings emitted them with hardcoded values (off / 100 / 0.001). A user-configured 1000-iteration loop with a 0.0001 convergence delta silently reset to "iteration off, 100, 0.001" on every HotXLS resave — breaking circular-reference formulas and convergence-sensitive financial models.
- Three new TXLSWorkbook properties.
EnableIteration: Booleanmirrors the checkbox;MaxIterations: LongWordis the iteration cap (clamped to the spec-legal 1..32767 range by the setter);MaxIterationChange: Doubleis the convergence delta (negative input snaps to the spec default 0.001). Defaults match the values the writer used to hardcode, so workbooks that never touch the new properties produce byte-identical output to v2.88.17.
Version 2.88.17
- Workbook calculation mode (Manual / Automatic / Auto except tables) now round-trips. The BIFF8 CalcMode record ($000D) carries the workbook-level "Formulas → Calculation Options" choice per MS-XLS §2.4.36. HotXLS previously had no dispatcher case for $000D at all, so the source file's value was discarded on load, and the writer always emitted a hardcoded
$0001(Automatic). A user-selected Manual mode silently reverted to Automatic on every HotXLS resave — bad for large workbooks where Manual is set deliberately for performance. The new ParseCalcMode handler stores the source value on the workbook and StoreCalculationSettings now emits the stored value instead of the constant. - New TXLSWorkbook.CalculationMode property. Read-write
LongWordproperty acceptingxlCalcManual(0),xlCalcAutomatic(1, default), orxlCalcAutomaticExceptTables(0xFFFF) — three new constants exported fromlxTNC.inc. The setter clamps unknown values to the spec default so a corrupt source file cannot leave the workbook in an undefined state. Existing code that never touches the property keeps the legacy "Automatic" default, so previous output is byte-identical for default workbooks.
Version 2.88.16
- Page Setup "Cell errors as" choice now survives the save cycle. The BIFF8 Setup record ($00A1) packs the Page Setup → Sheet tab "Cell errors as: displayed / <blank> / <dash> / <N/A>" dropdown into bits 10-11 of its grbit word as
iErrors, per MS-XLS §2.4.257. HotXLS previously dropped these bits on read and emitted them as zero on write, so a workbook the user had configured to print#DIV/0!as#N/Asilently reverted to "displayed" after every HotXLS resave. The reader now decodesiErrorsinto a newTXLSPageSetup.PrintCellErrorsproperty and the writer rebuilds the field from it. - New TXLSPageSetup.PrintCellErrors property. Read-write
LongWordproperty acceptingxlPrintErrorsDisplayed(0, default),xlPrintErrorsBlank(1),xlPrintErrorsDash(2), orxlPrintErrorsNA(3) — four new constants exported fromlxTNC.inc. Existing PageSetup callers that never touch the property keep the default ("displayed"), so the legacy print output is unchanged.
Version 2.88.15
- Cell comment alignment and orientation now round-trip. The BIFF8 TXO base record ($01B6) carries five typed flags in its
grbitword (hAlignment, vAlignment, fLockText, fJustLast, fSecretEdit) plus a separaterotword for text orientation, per MS-XLS §2.4.329. The reader previously dropped all of these and the writer pinnedgrbitto a hardcoded0x0012(left + top) with rotation always 0, so every save reset a centered or rotated comment back to left+top+horizontal regardless of source. The reader now decodes the typed fields on load and the writer composesgrbitfrom them, so the layout survives the save/reload trip. - Six new TMSOShapeTextBox properties for explicit comment styling.
HAlignment,VAlignment,Rotation,LockText,JustLastLine, andSecretEditare exposed as read-write properties on each comment shape;AddCommentdefaults to left+top+horizontal+unlocked so callers that do not touch them keep the previous appearance. - TXO writer now lays out reserved4 / reserved5 along spec field boundaries. Previous code emitted offsets 2-9 of the base record as two long writes (rot + reserved4 then reserved5); the writer now splits them into
Wordrot,Wordreserved4,Longreserved5 — the byte content is unchanged (still all-zero for Note shapes) but the source structure now matches MS-XLS §2.4.329 field offsets, making future TXO fixes harder to misalign.
Version 2.88.14
- TXO cchText offset corrected — the v2.88.13 comment-length fix now actually fires. The previous release introduced a sanity check for the TXO base record by reading
cchTextat offset 8, but MS-XLS §2.4.329 putscchTextat offset 10 (after grbit, rot and the 6 reserved / controlInfo bytes). Offset 8 falls inside the reserved5 long, which is always zero on Note-shaped objects, so every comment quietly slid into the defensive fallback that simply restored the pre-v2.88.13 behavior. The fix corrects the offset to 10 — long comments now genuinely stitch across Continue records and short comments no longer pick up trailing padding. No further behavioral change beyond what v2.88.13 was always supposed to ship.
Version 2.88.13
- Long cell comments now read back at full length. The BIFF8 TXO record ($01B6) splits its text body across the base record and one or more Continue records, with the character count published in the base record at offset 8 as
cchText. The previous comment reader skipped the base record entirely, treated DataList[1] as the entire text, and stopped there — so a comment longer than about 4100 wide characters was silently truncated, and a shorter comment whose Continue record carried trailing padding picked up junk at the end. The reader now consumescchTextas the authoritative character count, walks each Continue segment per MS-XLS §2.4.329 (the rgb break sits on a character boundary so no partial-character bookkeeping is needed), and stops as soon as the budget is satisfied so the formatting-runs block that follows the text never bleeds into the string. Comments with corrupted / missing base records still fall back to the legacy "all remaining bytes" path.
Version 2.88.12
- Cached strings longer than ~4100 characters from string-returning formulas now round-trip across Continue records. v2.88.11 wired up the read side of the BIFF8 String record ($0207) but only consumed the first record body, so a cached string whose UCS-2 bytes overflowed the 8224-byte BIFF limit and spilled into one or more Continue records ($003C) was silently truncated. The handler now walks the entire dispatch-level DataList: it reads cch and fHighByte from the base record, copies the body that fits there, then concatenates every following Continue segment as raw bytes per the MS-XLS §2.5.293 character-boundary guarantee — XLUnicodeString never re-emits fHighByte mid-string. Each segment is bounded by the remaining character budget so a stray oversized Continue cannot make the result longer than cch. Single-record strings (the common case) take the exact same fast path as before.
Version 2.88.11
- String-returning formulas now round-trip the cached text Excel computed. The previous release covered the number, boolean, error and empty variants of the BIFF8 FormulaValue cache (MS-XLS §2.5.133), but left the string variant on the to-do list because its cached value lives in a separate String record ($0207) that immediately follows the Formula record. ParseFormula now remembers the cell coordinates when it sees a string-variant Formula, and the new ParseString handler decodes the trailing $0207 record per MS-XLS §2.4.268 and routes the WideString into
FCachedFormulaValue.GetCellValuecan now fall back to the Excel-computed text whenever the HotXLS evaluator cannot reproduce it. A defensive reset at the top of ParseFormula drops any stale pending anchor so a malformed source file with a missing String record cannot misroute a later one. Records longer than 8224 bytes that overflow into Continue chunks still fall back to evaluator output as a known limitation.
Version 2.88.10
- BIFF8 Formula records now round-trip the cached value Excel computed. ParseFormula used to skip the 8-byte FormulaValue payload at offset 6 entirely, so the cached result of every formula in a loaded .xls (numbers, booleans, errors, empty) was thrown away and HotXLS had to re-evaluate every cell through its own formula engine. When that engine could not match Excel (unsupported XLM functions, broken external links, formulas the engine has not yet implemented) the cell silently became blank or zero. The reader now decodes the four numeric / boolean / error / empty variants per MS-XLS §2.5.133 and stores them on a new
TXLSCellRef.FCachedFormulaValuefield;GetCellValuefalls back to that cache only when the evaluator fails, so freshly-authored formulas still go through evaluation as before. The string variant continues to defer to the evaluator because the trailing String record ($0207) parser is not yet wired up.
Version 2.88.9
- BIFF8 Formula records with unresolvable values now flag fAlwaysCalc instead of the reserved1 bit. When the HotXLS evaluator could not calculate a formula at save time, the writer used to set
grbit = 0x0002— bit 1, which MS-XLS §2.4.127 reserves and tells readers to ignore. Excel duly ignored it, treated the placeholder cached value of 0 as authoritative, and silently displayed 0 instead of recalculating. The writer now setsgrbit = 0x0001(fAlwaysCalc, bit 0), so Excel re-runs the formula on next recalc and the cell shows the correct result.
Version 2.88.8
- PageSetup.PrintTitleColumns / PrintTitleRows now raise on bad input. Setting either property with a malformed range string (anything
ColDiapasonToValues/RowDiapasonToValuescould not parse) previously dropped the assignment silently — the workbook kept whatever Print_Titles range was already set, or no range at all, and the caller had no way to find out something went wrong. The setters now raiseException.Createwith a descriptive message naming the offending value, matching the existing raise style onTXLSRangeinvalid-index paths.
Version 2.88.7
- Row miyRw field now stays within the MS-XLS spec range. Pre-BIFF8 versions of Excel encoded "use the workbook default row height" by OR-ing bit 15 into miyRw, and HotXLS carried that idiom forward in the BIFF8 writer even though MS-XLS §2.4.221 caps miyRw at 8192 twips. Modern Excel emits a plain 255 (12.75 pt default) in the same case and uses the fUnsynced grbit bit to signal manual overrides, so the bit-15 hack made HotXLS output diverge from authoritative .xls files (a value of 32 959 = 0x80FF for "default" was technically out-of-spec). The writer now drops the OR and writes 255 directly; the reader still strips bit 15 on the way in for backwards compatibility with HotXLS v2.88.6 and earlier files plus rare third-party generators that copied the BIFF5 idiom.
- BIFF8 spec audit round 2 errata note added. Re-checking the [MS-XLS] field boundaries clarified two findings flagged as P2 in the original report (
dev-notes/XLS-BIFF8-Spec-Audit-Round2.md) are not actual bugs: the bit-15 set in the BOF history flag $000080C9 belongs to the verXLHigh field (bits 14-17, value 2 = "Excel 2002"), not the reserved1 tail (bits 19-31); and ParseFormula's use of a leading PtgExp token to detect shared / array formulas matches what the spec implies and disambiguates correctly via the following ShrFmla / Array record. Both items are now annotated as retracted.
Version 2.88.6
- Macro sheets and VBA modules now keep their BoundSheet8 ordinal slot. The reader previously dropped BoundSheet8 records whose
dtfield was 1 (macro sheet) or 6 (VBA module), so the HotXLS Sheets collection ran short by those entries. Any XTI cross-reference in ExternSheet that pointed at a sheet position past a macro sheet by file order then resolved to the wrong HotXLS worksheet after a SaveAs. The reader now adds a placeholder sheet for every BoundSheet8 record and stores the rawdtbyte on a newSheet.SheetTypeRawproperty; the writer emits the original byte back. Macro substream BOF records ($0040) are also treated as worksheet substreams during parsing so cells inside macro sheets are preserved rather than silently absorbed into the previous sheet.
Version 2.88.5
- BIFF8 Row record now round-trips fCollapsed, fExAsc, fExDes and fPhonetic. Previously the reader dropped fCollapsed (grbit bit 4) — the writer inferred it from outline-level transitions on neighboring rows, which misclassified rows the source file had explicitly collapsed or marked as boundaries. The high three bits (fExAsc at 28, fExDes at 29, fPhonetic at 30) were never read either, and were always emitted as zero because the writer truncated the 4-byte grbit to a single 16-bit ixfe slot. The reader now consumes all four bits per MS-XLS §2.4.221 and pins fCollapsed as explicit so the writer-side inference no longer overwrites Excel-asserted values. The writer packs ExAsc / ExDes / Phonetic into bits 12-14 of the ixfe Word, masking ixfe to the spec-allowed 12-bit range to prevent an XF index ever colliding with the new flags. Round-trip side effects: thick-top / medium-bottom borders driven by ExAsc / ExDes hints now survive HotXLS SaveAs, and phonetic info visibility on Japanese sheets stops resetting on every save.
Version 2.88.4
- BIFF8 Window2 fPaged bit now flags only the active sheet. Previous releases set
fPaged(MS-XLS §2.4.346 bit 10) on every visible sheet, which made HotXLS output diverge from what Excel itself writes (per spec the bit means "this sheet is currently displayed in the workbook window", so only the active-sheet substream should carry it). Excel readers fell through toWindow1.itabCurto identify the real active sheet anyway, so the misuse was tolerated, but Apache POI strict mode and other tooling that trustsfPagedas a primary signal now see the correct sheet flagged.
Version 2.88.3
- BIFF8 Window2 zoom and gridline color now round-trip. Previous releases dropped the WINDOW2 record fields past offset 6 on read and emitted hard-coded zeros on write, so any Excel-saved sheet zoom (75 percent, 125 percent, etc.) and gridline color customization reverted to defaults after a HotXLS SaveAs. The reader now consumes
icvHdr,wScaleSLVandwScaleNormalper MS-XLS §2.4.346: the active-view zoom feedsSheet.Zoomand the opposite-view value lands in a cached slot so switching between Normal and Page Break Preview in Excel restores the previous zoom. The writer emits the activeSheet.Zoomin the matching view slot and the cached value in the other, plus the preserved gridline color index.
Version 2.88.2
- Fixed BIFF8 HFPicture record opcode. Saved .xls files now emit header / footer background-image records with the correct
rt = 0x0866per MS-XLS §2.4.138. Previous releases used0x086C— the opcode for CellWatch — so Excel tried to parse the embedded OfficeArt BLIP as a watched-cell reference and silently dropped the picture (sometimes refusing to open the workbook when the embedded image bytes happened to violate CellWatch’s expected 16-byte structure). Workbooks that round-trip a header / footer image through HotXLS now keep the image intact.
Version 2.88.1
- Fixed BIFF8 Theme record opcode. Saved .xls files now emit the Theme record with the correct
frtHeader.rt = 0x0896per MS-XLS §2.4.326. Previous releases used0x0892— the opcode for StyleExt — so Excel interpreted the embedded OOXML theme payload as a cell-style extension and silently fell back to the built-in Office theme, losing any customized theme colors when round-tripping a workbook through HotXLS.
Version 2.88.0
- [MS-XLSX] cross-audit round 4 — theme part + sheet-name setter. Two follow-up fixes targeting issues left after rounds 1–3 (v2.51.0 / v2.52.0 / v2.54.1). Neither breaks an existing call site; one extends behavior at a setter, the other adds a new always-emitted part.
- Saved .xlsx packages now include an
xl/theme/theme1.xmlpart. styles.xml has always emitted<color theme="N"/>references for any font / fill / border / tab color set through the theme-index path (and for theme colors carried through from a parsed input .xlsx), but no theme part was written, so the references dangled. Excel tolerated the omission by falling back to its built-in “Office” theme silently, but Office File Validation flagged every reference as “theme part missing” and Apache POI strict mode rejected the workbook on first lookup.SaveAsnow emits the canonical Office (2007+) theme: 12-slot color scheme (systemwindowText/window, four Excel default colors, six accents, two hyperlink colors), Calibri Light / Calibri font scheme, minimum-valid format scheme (three fill / line / effect / background-fill entries each perCT_StyleMatrix).[Content_Types].xmlgains a matchingOverride;xl/_rels/workbook.xml.relsgains atheme/theme1.xmlrelationship that lands at the tail of the rid range, so existingXlsxFirstExternalLinkRidoffsets for sheets / sst / styles / vba / externalLinks are unchanged. - Direct
Sheet.Name := '...'assignment now sanitises and dedupes too. Round 3 (v2.54.1) added Excel naming-rule enforcement inTXLSXSheets.Add(AName), but theTXLSXWorksheet.Nameproperty setter was a rawwrite FName, so the most common mutation path that round didn’t cover —lxXlsxExport.pas:122 Target.Name := Source.Namein the XLS → XLSX round-trip — let through any name the BIFF8 source happened to carry (forbidden chars: \ / ? * [ ], length > 31, surrounding single quotes, reservedHistory, case-insensitive duplicates of an existing sheet). The property setter now routes through a newSetNamemethod that replays the sameXlsxSanitizeSheetName+' (N)'-suffix dedup logic (case-insensitive, exclude-self) against the parent workbook’s sheet collection. Detached worksheets (noFWorkbookback-pointer yet) skip the dedup step and just sanitise —TXLSXSheets.Addwill enforce uniqueness when the sheet attaches. Idempotent on already-valid names so reader-sideFSheets.Add(names[i])stays a pure round-trip.
Version 2.87.4
- XLS shared formulas now write a valid
ShrFmlabody. BIFF8 shared-formula output now includes the requiredcUsebyte before the shared parsed formula, so workbooks saved withUseSharedFormulasno longer shift the formula token stream by one byte. - XLS table style and filter metadata now matches the latest MS-XLS layout.
AddTablenow writesLIST12asList12TableStyleClientInfo, sets table and field AutoFilter flags inFeature11, and emits the empty per-columnFeat11FdaAutoFilterblocks Excel expects. - BIFF8 CF12 Data Bar / Color Scale / Icon Set rules now omit forbidden DXF payloads. HotXLS follows the MS-XLS rule that these CF12 types must carry an empty DXF block, while preserving their actual rule settings in the CF12 kind-specific tail.
Version 2.87.3
- XLS worksheet table metadata now follows the MS-XLS
Feature11table layout.AddTableoutput for .xls files now writes aFeatHdr11table header plusFeature11TableFeatureType/Feat11FieldDataItempayloads, removes the legacy$0867 FeatHdrtable marker, and stores table and column names asXLUnicodeStringvalues. - XLS table readback now understands the spec-correct field layout. HotXLS parses
TableFeatureTypetable names, ranges, totals-row state, and field captions, while keeping a fallback for older HotXLS-generated table records.
Version 2.87.2
- XLS BIFF8 continuation records now carry correct base lengths. Oversize XLS records split by the writer now update the first record length to 8224 bytes instead of leaving the original oversized length in the BIFF header.
- Large XLS worksheet tables now use spec-correct
ContinueFrt11chunks. WideAddTableoutputs with largeFeature11payloads now save with$0875 ContinueFrt11continuation records and reopen with table metadata preserved, matching the [MS-XLS]Feature11/ContinueFrt11layout.
Version 2.87.1
- XLSX worksheet grid limit now includes the last Excel row and column.
TXLSXRange,RCRange,EntireRow, andEntireColumnnow allow row 1048576 and column 16384 (XFD), matching [MS-XLSX] row-number and column-number bounds. Earlier the internal clamp used zero-based maxima, soXFD1048576and ranges touching the last row or last column were silently clipped to the previous row/column. - XLSX encryption docs and tests now match the implemented write path. Help pages and the regression suite now state that
SaveAsEncryptedwrites ECMA-376 Standard Encryption output for non-empty passwords, whileOpenEncryptedstill raises for encrypted packages and falls back for plain .xlsx files.
Version 2.87.0
- XLSX password-protected save now works (wave J phase 2, backlog #29).
TXLSXWorkbook.SaveAsEncrypted(FileName, Password)now writes a real ECMA-376 Standard Encryption-protected .xlsx file (Office 2007+ format). The complete [MS-OFFCRYPTO] §2.3.4 pipeline is in place: phase 1's SHA-1 50000-iteration password derivation + AES-128 verifier blocks + AES-CBC package body encryption + binaryEncryptionInfoserializer is now joined by phase 2's four\005DataSpacesmetadata streams (Version / DataSpaceMap / DataSpaceInfo / TransformInfo) and the OLE Compound File Binary writer that drops everything into a CFB container Excel opens with the user password. Workflow: caller invokesSaveAsEncryptedwith a non-empty password → HotXLS serializes the workbook into an in-memory plaintext .xlsx zip → encrypts via AES-128 CBC with a fresh random 16-byte salt and verifier → writes the CFB output viaStgCreateDocfile. Open in Excel 2007+: enters password → content opens. Wrong password: rejected.OpenEncrypted(decrypt side) is still a separate backlog item and continues to raiseEXlsxEncryptionNotImplementedwhen handed an encrypted file.
Version 2.86.1
- XLSX Standard Encryption — cryptographic foundation (wave J phase 1, backlog #29). Two new internal units land the cipher and key-derivation half of the ECMA-376 Standard Encryption write path.
lxAES.paswraps Brian Gladman's AES (linked fromthirdparty/Win32/aes*.obj+thirdparty/Win64/aes*.obj) and exposes AES-128 ECB single-block and AES-128 CBC encryption entry points.lxXlsxStdEncrypt.pasimplements pure-Pascal SHA-1 (FIPS-180-4), the [MS-OFFCRYPTO] §2.3.4.10 50000-iteration password derivation, the §2.3.4.7 verifier blocks (EncryptedVerifier+EncryptedVerifierHash), AES-CBC package body encryption (8-byte LE size prefix + zero-pad to 16-byte boundary + IV-of-zeros), and the §2.3.4.6 binaryEncryptionInfoserializer.TXLSXWorkbook.SaveAsEncryptedstill raisesEXlsxEncryptionNotImplemented— wave J phase 2 will add the OLE Compound File Binary writer (\005DataSpacescontainer streams) and wire the pipeline toSaveAsEncrypted. Until then, the new units are library-internal scaffolding callers should not depend on.
Version 2.86.0
- QueryTable and External Data Connection round-trip (wave I, backlog #28). Excel-created QueryTables and external data connections (Data → Get External Data from Web / SQL / OLEDB) now survive a HotXLS save/open cycle. Earlier the BIFF8 reader silently skipped
QsiSXTag($0802),QsiSXTagExt($0810), andDConn($0876), so the metadata that describes the upstream query was dropped on save and the table degraded to plain cell values. The reader now captures these records as opaque bytes and the writer emits them back into the worksheet substream (alongside the PIVOTVIEW block per [MS-XLS] §2.1.7.20.5) and the workbook globals (alongside the late globals block before EOF per §2.1.7.20.3). HotXLS still does not model the typed QSI / DConn structure programmatically — creating new QueryTables from caller code is deferred to a later wave — but workbooks that already contain them no longer lose refresh metadata. BIFF8 (.xls) only; BIFF5 unaffected.
Version 2.85.1
- Restore Win32 / Win64 library build. Two pivot-line-item helper variables in the wave H5.3 multi-data-field SXLI emitter were declared as separate
array of array of Wordanonymous types, which the Delphi compiler treats as distinct unnamed types and rejects on direct assignment withE2008 Incompatible types. The HotXLS library has not built since v2.84.1 on RAD Studio 12.0 through 37.0. The two variables are now declared together so they share a single anonymous type, fixing the assignment and restoring the .dcu / .bpl output for both Win32 and Win64 targets. No runtime behavior change — the emitted SXLI bytes are identical to v2.85.0.
Version 2.85.0
- Typed pivot subtotal API (wave H5.4). The typed
TXLSPivotFieldnow exposes aSubtotals: TXLSPivotSubtotalsset property alongside the existing rawSubtotalMask: Word. Callers can assign e.g.Field.Subtotals := [xlpsSum, xlpsAverage];instead of bit-packing the SXVDgrbitSubfield by hand. Twelve subtotal types are exposed (xlpsDefault, xlpsSum, xlpsCount/CountA, xlpsAverage, xlpsMax, xlpsMin, xlpsProduct, xlpsCountNums, xlpsStdDev, xlpsStdDevP, xlpsVar, xlpsVarP), each mapping to a single bit per [MS-XLS] §2.4.279. The rawSubtotalMaskstays the round-trip source of truth — reserved bits 12-15 are preserved on read/write. Closes the wave H4.3 known limit “row field subtotal SXVD.grbitSub configuration unavailable from API” and finishes the wave H5 pivot follow-ups.
Version 2.84.1
- Multi data-field SXLI expansion (wave H5.3). When a pivot table has more than one data field AND the data-field axis matches a given SXLI axis (
DataFieldAxisIsRow = True&& row axis, orDataFieldAxisIsRow = False&& column axis), the writer now replicates each cartesian-product combinationDataFieldCounttimes withiData = 0 .. DataFieldCount - 1, plusDataFieldCountgrand-total lines so each data field gets its own total. The non-hosting axis is unchanged. Excel and other readers now render multi-data-field pivots in the layout the caller intended instead of falling back on the previous “singleiData = 0per combination” approximation. Single-data-field pivots emit bit-identical output to v2.84.0. Delta compression (H5.1) folds the repeated combinations down to ~8 bytes per replica, so the expansion adds minimal byte overhead. Closes the wave H4.3 known limit “multi-data-field SXLI not correctly expanded”.
Version 2.84.0
- Pivot page-axis pre-selection API (wave H5.2). The typed
TXLSPivotFieldnow carries aPageItemIndexproperty that programmatically-built pivots can set to pre-select a specific cache item on the page-axis filter. DefaultxlPageItemAll(= $7FFD) leaves the filter at “(All)” so callers that never assignPageItemIndexbehave bit-identically to v2.83.2. The SXPI ($00B6) writer readsField.PageItemIndexinstead of hardcoding $7FFD, and the typed reader now populatesPageItemIndexfrom SXPI on Open so callers can inspect the saved page selection. Closes the wave H4.3 known limit “page filter caller-pre-selection unavailable from API”.
Version 2.83.2
- Smaller pivot SXLI records via delta compression (wave H5.1). The pivot writer now delta-compresses SXLI line items using the spec's cSic field — each line's leading cache-item indices that match the previous line in the same SXLI record are folded into a shared-prefix count, and only the trailing indices are emitted. Typical row-axis SXLI records (a few row fields × dozens of items) shrink by 30–60% with no change in semantics. Output is still chunked to stay under the 8224-byte BIFF record limit. The first line of every SXLI record always emits cSic = 0 (record-local, no cross-record back-reference), and the grand-total line is never delta-compressed.
Version 2.83.1
- Cell fill, font, and border colors set via RGB now round-trip through .xls save/open. The BIFF8 XFExt ($087D) writer was emitting each record's ixfe field using the in-memory XF array position, but the matching cell records on disk write ixfe using the post-pruning SaveIndex order. Whenever any XF earlier in the table was pruned during save (typical when one cell migrates from a transient XF to a final XF), every XFExt's ixfe ended up off by the number of pruned slots, and the rich color associated with each cell shifted to a neighboring cell on reload. The writer now resolves SaveIndex for each rich-slot XF and emits the matching on-disk ixfe, so RGB cell colors saved by HotXLS — interior fill, font color, border colors — survive a save / reload round-trip cleanly. XFExt records for XFs that got pruned entirely are now skipped rather than emitted with a dangling ixfe.
Version 2.83.0
- Pivot line items + page item state (wave H4.3). Programmatically-built pivot tables now emit SXLI ([MS-XLS] §2.4.275, $00B5) for both row and column axes, plus SXPI ($00B6) for the page axis. This gives the pivot view layout-fidelity: non-Excel readers see the exact display order the writer intended, large pivots open faster (Excel doesn't have to recompute line layout from scratch), and page filters persist with a default "(All)" selection per page field.
- Row + column SXLI enumeration. The writer enumerates the cartesian product of axis fields' cache items (innermost field varies fastest, matching Excel's nested-row layout) and emits one line per combination plus a trailing grand-total line marked with the SXLI nTypes fGrandTotal bit. Each line uses cSic = 0 (no delta-compression — simpler at the cost of a few extra bytes per line). Output is chunked into multiple SXLI records when the line count overflows the 8224-byte BIFF limit; a 16384-line safety cap prevents runaway expansion on pathological inputs.
- SXPI page-axis state. Emitted only when at least one field has Axis = xlpfaPageField. Each entry is 10 bytes (isxvd + iCache + ipos + objId) with iCache = $7FFD signalling "(All)" — the API surface for pre-selecting specific page items is deferred to wave H5. Pivots without page fields emit no SXPI (the worksheet ABNF lists it as optional).
- Closes wave H3 known limit "SXLI / SXPI not emitted". Combined with H4.1 (DataField number formats) and H4.2 (SXDBB per-record stream), wave H4 closes all three known limits in the H3 pivot-table writer. Pivot tables built via
IXLSWorksheet.AddPivotTablenow ship self-contained caches with full layout metadata.
Version 2.82.0
- Pivot cache self-contained per-record stream (wave H4.2). Programmatically-built pivot caches now emit SXDBB records ([MS-XLS] §2.4.272, $00C5 in cache context) carrying the per-record field-value indices in the spec's bit-packed encoding. Without SXDBB, Excel had to refresh the cache from the source range on Open — which worked when the source data was present and unchanged, but broke when the source was renamed / deleted or had drifted since the pivot was built. The cache is now self-contained.
- Bit-packed encoding per spec. Each field uses
ceil(log2(itemCount + 1))bits per record (sentinel space included). Records are byte-aligned — every new record starts on a fresh byte boundary. The writer chunks the stream into multiple SXDBB records to stay under the 8224-byte BIFF size limit, so caches with tens of thousands of source rows still serialize cleanly. - Round-trip path unchanged. Pivot caches read from disk (FromRawBlobs = True) continue to round-trip via the wave H1 raw-byte path — the new SXDBB writer only fires for typed caches built by
IXLSWorksheet.AddPivotTable. Mixed workbooks (one round-tripped cache + one new cache) emit both paths cleanly.
Version 2.81.0
- Pivot data field number formats (wave H4.1). Pivot data fields can now carry an Excel number format string so currency / percentage / date data renders with the right glyphs on Open instead of showing raw General-format values. New
IXLSWorksheet.PivotSetDataFieldFormat(DataField, FormatStr): Wordregisters the format in the workbook's NumFormats table (auto-add or reuse) and writes the resulting ifmt index toTXLSPivotDataField.NumberFormat. The typed writer already emits SXDI.ifmt, so this closes the loop. - Public NumFormats helper on TXLSWorkbook.
Workbook.RegisterNumFormat(FormatStr): Wordexposes the same auto-add behavior at workbook level for callers who need ifmt indices in non-pivot contexts. Empty FormatStr short-circuits to index 0 ('General'). - Caller usage.
df := pivot.AddDataFieldByName('Revenue', xlpaSum); Sheet.PivotSetDataFieldFormat(df, '$#,##0.00'); df := pivot.AddDataFieldByName('GrowthPct', xlpaAverage); Sheet.PivotSetDataFieldFormat(df, '0.00%');
Version 2.80.1
- Icon Set conditional formats round-trip cleanly through HotXLS save/open. The v2.49.0 audit changed the CF12 writer to emit
ct=$06for Icon Set rules per [MS-XLS] §2.4.43 (the spec-correct value;$05is reserved for Filter rules), but the matching reader path still only recognized the legacy$05value. As a result, .xls files written by HotXLS v2.49.0..v2.80.0 lost their Icon Set rules on reload — the rule kind degraded to a plain cell-is rule and the icon-set type / Reverse / ShowOnly flags reset to defaults. The reader now accepts both$05(legacy HotXLS pre-v2.49.0) and$06(spec / v2.49.0+) so workbooks from either generation re-open cleanly.
Version 2.80.0
- Programmatic pivot table creation API (wave H3). New
IXLSWorksheet.AddPivotTable(SourceRangeA1, DestRow, DestCol, Name): TXLSPivotTablemethod walks the supplied source range, infers each column's data type from the cell values, auto-builds aTXLSPivotCache(or reuses an existing one bound to the same range), and creates a typed pivot table on the destination sheet with one TXLSPivotField per cache field (axes initially unset). - Caller wires axes / data via convenience methods.
pivot.AddRowField('Region'),pivot.AddColumnField('Quarter'),pivot.AddPageField('Year'),pivot.AddDataFieldByName('Revenue', xlpaSum).SetFieldAxis+AddDataFieldare exposed for finer control. Field axis enum: xlpfaRowField / xlpfaColumnField / xlpfaPageField / xlpfaDataField / xlpfaNone. Eleven aggregation functions: Sum, Count, Average, Max, Min, Product, CountNums, StdDev, StdDevP, Var, VarP. - Typed writer emits SX* records + cache substream. When
pivot.FromRawBlobs = False(the default for programmatically created tables), the writer serializes the typed model into SXView ($00B0) + SXVD ($00B1) per field + SXVI ($00B2) per item + SXIVD ($00B4) row/col index arrays + SXDI ($00C5) per data field + SXEx ($00C6) into the worksheet footer. The matching pivot cache (cache.FromRawBlobs = False) emits a fresh BOF (dt=$0006) + SXDB ($00C6) header + SXFDB ($00C8) + SXFDBType ($00FB) + per-item SXSTRING / SXNUM / SXBOOLEAN / SXERR / SXDATETIME / SXEMPTY + EOF substream appended after the worksheet bodies. - Round-trip safety preserved. Pivot tables and caches read from disk keep their wave H1
FromRawBlobs = Trueflag and continue to round-trip via the raw-byte path. Only the new programmatically created pivots flow through the typed writer. Mixed workbooks (one round-tripped pivot + one new pivot via AddPivotTable) emit both paths cleanly. - Wave H3 limitations. Source range types other than rectangular A1 (named ranges, external workbook refs) are out of scope. SXLI / SXPI (line items / page items) are not emitted for typed pivots — Excel computes them on Open. Formatting customization (data field number format beyond per-field NumberFormat) is exposed as a field but the writer treats it as 0 (general). Multiple pivots sharing a cache are supported but caller must wire the same source range string each time.
Version 2.70.0
- Pivot table typed read model (wave H2). Building on the wave H1 raw-byte preservation, the reader now also populates a typed object model so callers can introspect pivot table structure without bytecode parsing. New types —
TXLSPivotTable,TXLSPivotField,TXLSPivotItem,TXLSPivotDataField,TXLSPivotCache,TXLSPivotCacheField,TXLSPivotCacheItem,TXLSPivotCaches,TXLSPivotTables— surface via the newIXLSWorksheet.PivotTablesandTXLSWorkbook.PivotCachesproperties. - What the model captures. Per pivot table: position (FirstRow / Col / LastRow / Col / FirstDataRow / FirstDataCol), Name, DataCaption, CacheId, and an ordered list of
Fields(each with Name, Axis ∈ {xlpfaRowField,xlpfaColumnField,xlpfaPageField,xlpfaDataField,xlpfaNone}, Items[], SubtotalMask, CacheFieldIndex). Data axis emits a parallelDataFieldslist, each carrying anAggregationfrom the 11-valueTXLSPivotAggregationenum (xlpaSum / Count / Average / Max / Min / Product / CountNums / StdDev / StdDevP / Var / VarP). - Cache model. Each
TXLSPivotCacheholds CacheId, SourceRange (Sheet + 0-based row/col bounds), RecordCount, and a list ofFields. Each cache field carries its data type (xlpcftNumber / String / Boolean / Error / Empty / DateTime / Mixed), Name, NumberFormat index, and the unique-value Items[]. Per-record indices into each field's Items[] are exposed viaCache.RecordIndices[Record, Field]for programmatically-built caches; round-tripped caches read from disk leave per-record indices unpopulated and rely on the raw-byte path for fidelity. - Round-trip safety. Pivot tables that came from raw bytes have
FromRawBlobs = True— the writer continues to replay the wave H1 raw blobs verbatim. The typed model is populated as a best-effort projection for caller introspection. The typed-model writer that produces wire bytes from the model is scoped to wave H3 (programmatic AddPivotTable API). - New lxPivot.pas unit. All pivot types live in their own unit alongside lxChart / lxFormula / lxNames. lxHandle re-exports the public types so existing imports keep working.
Version 2.69.0
- Pivot table round-trip preservation (wave H1). HotXLS now keeps every record that makes up a pivot table when an Excel-created .xls travels through Open → Save. Before this release, the reader silently dropped unrecognized SX* records (SXView, SXVD, SXVI, SXIVD, SXLI, SXPI, SXDI, SXAddl, etc.) and skipped the entire Pivot Cache substream, so re-saving an Excel pivot file left Excel with broken pivot tables on next Open. The reader now captures all of these as opaque bytes and the writer replays them verbatim, so the pivot table on the cover sheet still renders correctly in Excel after a save / reload cycle.
- Worksheet PIVOTVIEW block. SX* view records ($00B0–$00B2, $00B4–$00B6, $00C5–$00C8, $00D0–$00D2, $00D5, $00E3, $00F0, $00FB, $0100, $0864) are captured in stream order and emitted in the writer's per-sheet footer (between the Tables block and the trailing EOF), matching the [MS-XLS] §2.1.7.20.1 worksheet ABNF position for PIVOTVIEW. Oversize SXAddl payloads ride through the existing Continue ($003C) splitter the same way HFPicture does.
- Pivot Cache substreams. BOF (dt=$0006) followed by SXDB / SXFDB / SXFDBType / SXSTRING / SXNUM / SXBOOLEAN / SXERR records up to the matching EOF is captured as one cache bucket; multiple caches in the same workbook each get their own bucket. On save the writer rebuilds the full BOF / records / EOF substream for each captured cache and appends them to the Workbook OLE stream after the per-sheet bodies, where [MS-XLS] §2.1.7.20 expects them.
- Scope: raw-byte preservation only (H1). Wave H1 deliberately does not parse pivot data into a typed model — TXLSPivotTable / TXLSPivotField / TXLSPivotCache and a programmatic AddPivotTable API are scoped to future waves H2 / H3. Until then HotXLS callers can't introspect or modify pivot table structure, but the existing Excel-built pivot tables round-trip through HotXLS without loss.
Version 2.68.0
- Chart backlog wave F sub-feature 3: Series error bars.
TXLSChartSeriesInfogrew anErrorBars: array of TXLSChartErrorBarsInfofield. Each entry produces a SerErrBar record ([MS-XLS] §2.4.105, $105B) inside the parent Series block, after Trendlines and before the Series End, in caller order. A typical column chart with Y error bars uses two entries (Sertm=1 Y+ and Sertm=2 Y-); scatter / bubble charts add X+/X- pairs (Sertm=3 / 4). - Five value sources.
Ebsrcselects 1=percent, 2=fixed value, 3=stddev, 4=stderr, 5=custom.NumValuecarries the magnitude (10.0 = 10% when Ebsrc=1, raw scalar for fixed value, multiplier for stddev). For custom (Ebsrc=5) theCnumfield carries the count of custom data points; spec says MUST=0 for the other sources, so the builder forces non-custom entries to Cnum=0 regardless of caller input. - T-cap rendering toggle.
FTeeTopcontrols whether Excel renders T-shaped caps on the bar ends (Excel's visual default = on). Useful when caller wants the more minimal "tick-only" style. - Custom value series (Ebsrc=5) caveat. The minimal builder emits the SerErrBar record with the chosen Cnum but does not yet emit the supporting BRAI cluster that points at the custom value array. Custom error bars therefore render with default magnitude until a future phase wires up the BRAI follower. Percent / fixed / stddev / stderr (Ebsrc 1..4) are fully functional in this release.
Version 2.67.0
- Chart backlog wave F sub-feature 2: Series trendlines.
TXLSChartSeriesInfogrew aTrendlines: array of TXLSChartTrendlineInfofield. Each entry produces a Trendline record ([MS-XLS] §2.4.328, $2050) inside the parent Series block, after SerToCrt and before the Series End, in caller order. Multiple trendlines on a single series are legal and Excel renders them all. - Six regression types.
Regtselects 0=polynomial, 1=exponential, 2=logarithmic, 3=power, 4=moving-average, 5=linear. For polynomial theNOrderfield sets the degree (2..6); for moving-average it sets the period (2..N). For the other typesNOrderis ignored but spec requires 1..6, so the builder defaults zero/out-of-range values to 1 / 6 respectively. - Equation and R² display + forecasting.
FDispEq/FDispRSqrtoggle the on-chart equation and R² labels.NumForecast/NumBackcastset the forward / backward extrapolation periods (Double, ≥ 0).NumInterceptsets the y-intercept; pass NaN ($7FF8000000000000) for "automatic". - Trendline label as inline LPWideString. The
Namefield is written into the Trendline record's sName slot ([MS-XLS] XLUnicodeString: cch:2 + grbit:1 + UTF-16 body). Empty Name leaves Excel to auto-generate a legend entry like "Linear (Series1)". - AddSeries internal refactor. The internal
AddSerieshelper now takes a singleTPreparedSeriesInforather than the previous (Name, CatArea, ValArea) triple, so future Series-attached records (next phase: error bars) can flow through one parameter without further signature churn.
Version 2.66.0
- Chart backlog wave F sub-feature 1: 3D chart types. Programmatically built chart sheets can now request
xlsChartType3DColumn,xlsChartType3DBar,xlsChartType3DLine, orxlsChartType3DPieviaAddChartSheet(Name, ChartType, ...). The 3D variants reuse the same on-the-wire BARCHART / LINECHART / PIECHART chart-type record as the 2D variants but the surrounding CHARTFORMAT block now also emits a Chart3d record per [MS-XLS] §2.4.46 to switch Excel into its 3D rendering pipeline. - Default 3D geometry mirrors Excel's "Insert > 3D Column". 20° rotation, 15° elevation, perspective off, cluster on, autoscale on, height and depth both 100% of width, gap 150%. Walls and right-angle axes match Excel's default. The geometry is hard-coded for now (no caller knob); the next phase can promote it to a record-style override.
- Chart3DBarShape ($105F) restored conditionally. v2.55.0 removed the unconditional Chart3DBarShape emission because [MS-XLS] §2.4.47 mandates "MUST be ignored if the current substream does not contain a Chart3d record" and 2D charts don't have one. Wave F restores it for 3D Bar / 3D Column only (riser=rectangle, taper=none), emitted adjacent to Chart3d inside CHARTFORMAT so the spec relationship is visible from the wire layout. 3D Line and 3D Pie still skip it (no riser/taper concept).
- 3D Pie skips both axes. The axes-used count and axis emission paths now treat
xlsChartType3DPiethe same asxlsChartTypePie— axes-used = 0, no category / value axis emitted. Without this gate Excel rendered a 3D pie with stray axis lines bleeding through the chart.
Version 2.65.0
- Formula array constants round-trip with their data.
PtgArraytokens ($20 / $40 / $60 — V / R / A class) in formula streams now carry theirPtgExtraArraypayload through both read and write paths.SUM({1,2,3}),=MMULT(...),{=COUNTIF(...)}and other array-literal formulas preserve the literal values in.xlsfiles written by HotXLS instead of round-tripping with empty rgcb sections. - Array data hangs off the syntax tree.
TXLSSyntaxItemgrewArrayCols/ArrayRows/ArrayValuesproperties (with the matchingTXLSArraySerValrecord describing num / str / bool / err / empty cells per [MS-XLS] §2.5.198.122 SerAr). Tools that walk the syntax tree can introspect array constants directly without needing the separate external-data container. - Programmatic array-literal construction. Code that builds
TXLSSyntaxItemtrees via the API can populateSA_ARRAYitems withSetArrayDimensions+ArrayValues[i]and HotXLS emits the matching PtgExtraArray in the rgcb section automatically. The wire ordering matches the [MS-XLS] §2.5.198.59 rule that PtgExtraArray entries are sequenced by the position of their PtgArray token in the rgce stream. - Existing read path stays correct for mixed rgcb. The new code only consumes PtgExtraArray entries that correspond to PtgArray tokens it parsed; any other extra data (e.g. PtgExtraMem from PtgMemArea / PtgMemFunc, still unsupported) is left alone for downstream handling, and a malformed rgcb falls back to leaving array values empty rather than discarding the parsed tree.
Version 2.64.0
- Chart backlog wave D phase 7: drawing theme color get / set round-trip within a process. Phase 5 introduced
XlsApplyThemeColorToDrawingas a set-only helper because BIFF8 OfficeArt is a pre-Office-2007 binary format with no theme color slot on disk. The setter still down-casts to RGB for the on-disk OfficeArt opt blob, but the workbook now also keeps an in-memory side-table that remembers the original theme idx + tint per(TMsoShapeContainer, OfficeArt property id)pair. A newXlsGetDrawingThemeColorreader consults that side-table so the same process can pull back the theme intent it just wrote — useful for in-app render code that wants to highlight theme-driven colors or expose them in property panes. After Save / Open the side-table starts empty again and the getter returns False, falling through toColorFormat.RGB. - New caller API.
XlsGetDrawingThemeColor(ColorFormat, Workbook, out ThemeIdx, out Tint): Booleanjoins the existingXlsApplyThemeColorToDrawing. The setter signature is unchanged; it now also records the entry intoTXLSWorkbook.FDrawingThemeColors(a dynamic array of records keyed by container pointer + property id). SubsequentXlsApplyThemeColorToDrawingcalls on the same slot overwrite the entry, so the table tracks the latest intent rather than accumulating history. - Stable identity through new internal properties.
TXLSColorFormatgrew read-only_Containerand_Pidaccessors so the helpers can compose the side-table key without the caller fishing private fields out of the class. Underscore prefix marks them as the same internal-API tier as_StringTable/_XFList/_BiffTheme: stable enough for library glue code, not part of the public contract. - Why this is "single-process round-trip" only. The BIFF8 OfficeArt format only stores indexed / RGB / scheme / sysColor in its 4-byte ColorRef; there's no extension record analogous to XFExt $087D where the theme idx + tint could live. So the disk round-trip cap is structural, not a HotXLS gap. The Phase 7 side-table bridges the in-process gap so app-internal code can pretend the format supports theme colors as long as it lives within one workbook session.
Version 2.63.0
- Chart backlog wave D phase 6: compound border selectors round-trip RGB / theme through XFExt. The last theme-color known-limit is closed:
Border[xlAround],Border[xlInsideVertical],Border[xlInsideHorizontal], andBorder[xlInsideAll]now register the same RGB or theme color against every physical edge slot they target instead of falling back to the indexed-palette path. The newTXLSXfRichSlotSet(set ofTXLSXfRichSlotID) lifts the pending-rich stash from a single slot to a set, so one cell-loop pass onSetRangeXFBordersPropertyregisters up to four XFExt slots in one go. - Compound selector mapping. The new
BorderEdgeSlotSet(BorderIndex): TXLSXfRichSlotSethelper centralises the selector → slot translation:xlEdgeLeft / Right / Top / Bottom→ singleton sets (unchanged from phase 2).xlDiagonalDown / Up→[rsDiag](BIFF8 FullColorExt only carries a single diagonal color).xlAround→[rsTop, rsBottom, rsLeft, rsRight](outer border).xlInsideVertical→[rsLeft, rsRight](cell-to-cell vertical separators).xlInsideHorizontal→[rsTop, rsBottom](horizontal separators).xlInsideAll→[rsTop, rsBottom, rsLeft, rsRight].
Border.SetColorandBorder.SetThemeColorshare the same mapping, so RGB and theme paths behave identically across all selectors. - TXLSBorders.SetColor restored to a single dispatch. Phase 2 had to fan
Borders.Color := Vout to four per-edgeGetBorder(edge).Color := Vcalls because the pending-rich stash only carried one slot at a time. With the set form,Borders.SetColornow stashes the full{rsTop, rsBottom, rsLeft, rsRight}set and callsSetColorIndexonce —SetBordersColorIndextakes the optimal wire path (single cells loop with BorderMask=15), andConsumePendingRichSlotregisters all four XFExt slots on each touched XF in the same pass. ~4x fewer cell-loop iterations than the phase-2 fan-out, byte-identical XFExt result. - ConsumePendingRichSlot iterates the set. The slot-registration loop walks the eight enum members and dispatches each one present in
FPendingRichSlotthroughRegisterXFExtSlot. Boundary cells whose XF didn't actually change during a compound-selector update still fire the callback — that's harmless because the XFExt slot is only visually relevant when the cell genuinely has the matching border line, and the indexed wire is set by the per-cellBorderMaskExcel already understands.
Version 2.62.0
- Chart backlog wave D phase 5: custom theme XML round-trip + drawing theme helper. Three pieces close the last theme-color gaps that phase 4 left open. Custom theme palettes set in Excel via Page Layout → Colors now survive the load cycle: the parser walks the OOXML
<a:clrScheme>embedded in the BIFF8$0892Theme record and updates the workbook's 12-slotFBiffThemein place. The matching writer emits the same record when the palette has drifted from the default Office scheme, so caller-customized themes round-trip through HotXLS without falling back to defaults. A newXlsApplyThemeColorToDrawinghelper lets chart / shape callers address drawing colors with the same theme idx + tint vocabulary that cells already use. - Reader: OOXML theme XML parsing.
ParseThemestitches the record body (frtRefHeaderU + dwThemeVersion + variable-length XML payload, may span Continue records) into a single byte buffer and runs a targeted substring scan for the twelve<a:clrScheme>children (dk1, lt1, dk2, lt2, accent1..6, hlink, folHlink). For each slot it prefers<a:srgbClr val="RRGGBB"/>and falls back to<a:sysClr ... lastClr="RRGGBB"/>, then convertsRRGGBBto the HotXLS COLORREF layout and callsFBiffTheme.SetThemeRGBColor. Bodies that don't look like OOXML (missing<a:clrScheme>sentinel, ZIP-compressed variants, etc.) leave the palette at its default scheme so common renderings stay correct. - Writer: Theme record emission with skip-when-default.
TXLSWorkbook.StoreThemeemits a$0892record only whenIsBiffThemeDefaultreports the palette has drifted from the Office default scheme. The OOXML body is built from a deterministic template: twelve<a:srgbClr>entries (chosen over the Excel-native sysClr form for dk1/lt1 because it round-trips through the same parser without needing a sysColor name resolver), plus the minimal legal<a:fontScheme>/<a:fmtScheme>stanzas the schema requires. Untouched workbooks emit no record and stay byte-compatible with pre-Phase-5 output. - Drawing theme color helper. The new free function
XlsApplyThemeColorToDrawing(ColorFormat, Workbook, ThemeIdx, Tint)resolves a theme idx + tint through the workbook palette and writes the resulting RGB onto aTXLSColorFormat(shape fill / line / chart color). BIFF8 OfficeArt is a pre-Office-2007 binary format with no native theme color encoding, so this is set-only: read-back returns the resolved RGB rather than the theme slot, and the round-trip theme reference is lost on disk. For full theme round-trip useIXLSInterior.SetThemeColor/IXLSFont.SetThemeColor/IXLSBorder.SetThemeColoron cells, which leverage XFExt $087D. - Wave D fully closed in five phases. Phase 1 (v2.58.1) added the case-table acknowledgement, Phase 2 (v2.59.0) wrote XFExt for RGB cell colors, Phase 3 (v2.60.0) closed the RGB reader loop, Phase 4 (v2.61.0) shipped the theme color path on cells, and Phase 5 (this) completes the picture with custom theme XML round-trip plus the drawing-side helper. Cells now match Excel's rendering pixel-for-pixel when round-tripping any combination of palette / RGB / theme colors that the BIFF8 spec can express; drawings carry the visual intent forward through the helper but pay the OfficeArt format's RGB-only price on read-back.
Version 2.61.0
- Chart backlog wave D phase 4: BIFF8 theme color support (theme idx + tint via XFExt $087D). Office's 12-slot theme palette (lt1, dk1, lt2, dk2, accent1..6, hlink, foHlink) now round-trips on the .xls side: callers can address theme colors directly with the new
Interior.SetThemeColor(themeIdx, tint)/Interior.SetPatternThemeColor/Font.SetThemeColor/ per-edgeBorder.SetThemeColorAPIs, and the cell color getters resolve the same theme idx + tint back through the workbook's BIFF theme palette when reading. The XF record still receives a best-match indexed icv (so legacy readers like Excel 2003 keep rendering an approximate color), while Excel 2007+ recovers the exact theme intent through the XFExt FullColorExt (xclrType=3) future-extension record — same backward-compatibility trick Microsoft uses when "Save As" downgrades a theme-styled .xlsx to .xls. - New caller API. Five new
SetThemeColorentry points join the existing RGB / index setters:IXLSInterior.SetThemeColor(ThemeIdx, Tint),IXLSInterior.SetPatternThemeColor(ThemeIdx, Tint),IXLSFont.SetThemeColor(ThemeIdx, Tint), andIXLSBorder.SetThemeColor(ThemeIdx, Tint)on each border edge.ThemeIdxis the 0-based theme slot (0=lt1, 1=dk1, 2=lt2, 3=dk2, 4..9=accent1..6, 10=hlink, 11=foHlink);Tintis the spec-style -1.0..+1.0 lighter/darker adjustment that gets encoded into FullColorExt.nTintShade as Q15 signed integer. - Workbook BIFF theme palette.
TXLSWorkbooknow carries anFBiffTheme: TXLSThemeColorsinitialized to the default Office theme scheme that the XLSX side has used for years throughTXLSColorManager.FTheme. Cell color getters resolve theme idx + tint through this palette viaGetThemeRGBColor(idx, tint)(which already applies HSL-based tint adjustment inlxRgb.GetRGBTInt). Internal_BiffThemeproperty exposes the palette for parser-side updates. - Theme record ($0892) acknowledged on read.
lxRead.ParseThemetakes$0892out of the default-skip path so the workbook-globals case table treats it as a known record. The embedded OOXML theme XML body itself is left unparsed for now — the 12 default Office theme colors already match the most common source and Excel-set theme cells render correctly through them. Workbooks with a fully customized theme will surface theme cells through the default palette here (documented known limit; XML-based theme parsing is a follow-up wave). - Why no Theme record on write. HotXLS-saved .xls files don't emit a Theme record. Excel 2007+ falls back to the default Office theme when no $0892 is present, which exactly matches HotXLS's
FBiffThemedefault scheme, so cells usingSetThemeColorrender with the right color in Excel without us having to ship the embedded XML. Files therefore stay smaller and the writer side has no XML-emit dependency.
Version 2.60.0
- Chart backlog wave D phase 3: XFExt ($087D) read-side RGB / theme recovery closes the round-trip. The Phase 1 acknowledgement and Phase 2 emitter are now joined by a parser that actually consumes each
$087Drecord into the workbook XF rich-color side-table. Files saved by Excel with RGB cell colors (fill, font, individual borders) now surface throughcell.Interior.Color/Interior.PatternColor/Font.Color/ per-edgeBorder.Colorat the exact RGB Excel wrote, instead of the indexed-palette approximation HotXLS used to return for the same cells. Wave D is now fully closed in both directions. - Reader:
ParseXFExtwalks ixfe + cexts + rgExt. The parser handles the eight color ExtProp extTypes ($0004 fillFg, $0005 fillBg, $000D font, $0007/$0008/$0009/$000A/$000B for top / bottom / left / right / diagonal borders) and decodes each one's FullColorExt body (xclrType, nTintShade as signed Q15, xclrValue). xclrType 2 (RGB) and 3 (theme) get registered on the workbook side-table against the record'sixfe; other extTypes ($0006 FillGradient, $000E FontScheme, $000F TextIndent) skip cleanly so a future wave can add them without disturbing this code path. Defensive bounds checks oncbmean a malformed record terminates parsing of its tail without reading past the declared length. - Cell color getters consult the side-table first. The eight color getters that previously routed straight through the indexed-palette path (
GetCustomColor(GetColorIndex)) now resolve their XF index, queryTlxFormatList.TryGetRichSlotfor the matching rich-color slot, and applynTintShadevia the existingGetRGBTInthelper when the slot carries an RGB value. xclrType=3 (theme) falls through to the indexed path: the BIFF workbook has no theme model on this call chain, so the palette approximation remains the best available answer for theme cells. Cells whose XF was never touched by a Phase 2 setter or a parsed XFExt produce the same byte-identical read result as before. - Wave D is closed. Save preserves caller-set RGB (Phase 2). Load preserves Excel-set RGB (Phase 3). Workbooks with no rich color information emit the same byte stream as before. The only remaining caveats stay documented as known limits: theme colors still down-grade through the BIFF palette because there is no theme color manager on this side of the codebase, and compound border selectors (
xlAround/xlInside*) tag a single ambiguous side-table slot rather than per-edge — both rare enough to leave for a future wave.
Version 2.59.0
- Chart backlog wave D phase 2: XFExt ($087D) emission for BIFF8 cell colors. When application code sets
cell.Interior.Color := $00FF6633(or any other RGB) on the BIFF8 path, the writer now emits a$087DXFExt record alongside the indexed-palette XF entry so Excel reopens the workbook with the exact RGB the caller passed in, rather than the closest 56-color palette approximation HotXLS used to fall back to. Covers all four cell color surfaces:Interior.Color/Interior.PatternColor/Font.Color/ per-edgeBorder.Color(top / bottom / left / right / diagonal) and the convenienceBorders.Colorwhich now distributes to the four primary edges so each gets its own XFExt slot. - How the round-trip stays safe. The BIFF8 cell setter chain still produces the indexed-palette XF wire output it always did — readers (HotXLS or anything else) that ignore
$087Dsee the legacy approximation. The XFExt record sits next to the XF and carries the original RGB as a 16-byte FullColorExt (xclrType=2, xclrValue=$00BBGGRR, nTintShade=0) wrapped in an ExtProp with the spec-correct extType for each slot ($0004 fillFg, $0005 fillBg, $000D font, $0007/$0008/$0009/$000A/$000B for top/bottom/left/right/diagonal borders). Cells that never took the color-setter path still emit no XFExt — workbooks with no rich colors produce the same byte stream as before. - Storage and bookkeeping. A new per-XF rich-color side-table in
TlxFormatListholds the original RGB / theme info the cell setter passed in, keyed by the workbook's hash-resolved XF index.TXLSRange.SetXFProperty/SetRangeXFBordersPropertygrew an optionalOnNewIndexcallback so the cell setter can register the rich color against every XF index the cell ends up on (multi-cell ranges that hash to multiple distinct XFs all get tagged). The side-table is consulted inStoreWorkbookright after STYLES and before PALETTE, matching the*XFExtposition in [MS-XLS] §2.1.7.20.3 Workbook Globals ABNF. xlExcel5 output is untouched (BIFF5 has no XFExt record). - Known limit. The reader side stays at the wave-D-phase-1 acknowledgement:
ParseXFExtconsumes$087Drecords from the input stream but does not yet feed the recovered RGB back into the workbook's style objects, because doing so in the hash-keyed style model risks invalidating existing XF entries. Files saved by HotXLS now look correct in Excel; files coming in from Excel still surface the indexed-palette approximation when read back throughcell.Interior.Color. Closing that read-side gap is a separate follow-up (Phase 3) that requires reworking the style hash to admit per-XF rich color before the first hash anchor.
Version 2.58.1
- Chart backlog wave D (read-side acknowledgement only): XFExt ($087D) no longer routed through the default-skip path. A new
ParseXFExtbranch in the BIFF8 workbook-globals case table consumes$087Drecords that Excel writes alongside RGB and theme-colored cell styles. The reader still leaves the indexed-color XF record unchanged — full RGB / theme precision recovery onto the workbook's hash-key style model is deferred to a separate wave because it would require the BIFF writer-side style classes to grow an RGB / theme color field that doesn't exist today. Files coming from Excel with rich colors continue to load with the indexed approximation HotXLS has always produced, but the spec-conformance gap of "treating$087Das unknown" is closed. - Why not the full wave-D scope. The plan envisioned a paired write + read implementation, but exploration found that the BIFF write pipeline (
TXLSWorkbook.FXFList: TlxFormatList) encodes XF style data as a string-key with hex-packed indexed color slots and has no analogue of the XLSX-sideTXLSStyleColor(which carriesColorType/ColorValue/TintAndShade). Adding XFExt emission would mean either bridging the two style models or extendingTlxFormatListwith per-XF rich color side-tables, both of which are larger refactors than a single wave can absorb. The change here therefore stays surgical: lxRead recognizes the record, lxStyleXf and lxHandle are unchanged.
Version 2.58.0
- Chart backlog wave C: SST rich-text and phonetic round-trip (BIFF8 XLUnicodeRichExtendedString). Rich-text formatting set in Excel (a cell whose individual characters carry their own font / color, the "two colors in one cell" effect from the Format Cells dialog) sits in the shared string table as an
XLUnicodeRichExtendedStringwith an attachedrgRunFormatRun array. Far-East phonetic info (the furigana / hiragana annotation above kanji) attaches as anrgExtRstblock on the same record. HotXLS previously dropped both on load and re-emitted the string as plain text on save, so opening then resaving a workbook flattened every multi-format cell to a single font / color. The shared-string parser now preserves both trailing blocks per unique string, and the writer re-attaches them when the string fits a single SST record. - Reader walks Continue boundaries without re-reading flag bytes. A new
CollectRawTailhelper insideParseSSTpulls the rawrgRunbytes (cRun × 4 bytes per FormatRun) followed by the rawrgExtRstbytes, spilling across Continue ($003C) records as needed — neither block is re-prefixed with the string’s flag byte at a Continue boundary per [MS-XLS] §2.4.58, which the old per-byte skip logic could not preserve correctly whenrtorszovershot the current record. The preserved tails are registered against the workbook SST through the newTXLSStringTable.AddWithExtras, which deliberately does not bump the cell-reference counter so subsequentLabelSSTparsing still produces correctcstTotalon save. - Writer attaches fRichSt / fExtSt + cRun / cbExtRst + rgRun + rgExtRst when the whole record fits in one BIFF chunk.
AddSSTItemnow factors the rich / phonetic tail size into its overflow check; strings whose body + tails fit a single 8224-byte SST chunk emit the full preserved layout, and Excel re-opens the workbook with the original formatting intact. Strings that overflow fall back to plain text without formatting (the splitter would otherwise need to interleave Continue flag bytes with the still-running rgRun byte stream, a state machine the current writer doesn't have); this is the explicit known limitation documented in the wave-C plan and affects only very long rich-formatted strings (> ~4000 wide chars). - Continue flag byte corner case fix (carried over from the v2.54.0 SST audit). Per [MS-XLS] §2.4.58 the leading flag byte of an SST Continue record carries only the
fHighBytebit (bit 0); bitsfExtSt(bit 2) andfRichSt(bit 3) MUST be ignored because those structures exist exactly once at the head of the first record.ParseSSTnow masks the Continue flag byte with$01, preventing a stray high bit from making the reader skip ahead 2 or 4 bytes looking for cRun / cbExtRst fields that aren’t there and misaligning every byte that follows.
Version 2.57.0
- Chart backlog wave G: Header / Footer pictures survive the round-trip (BIFF8 HFPicture). When Excel saves a worksheet with a header or footer background image (Insert → Header & Footer → Picture, then the company logo style), the picture sits in an
HFPicture($086C) record that embeds an OfficeArt BLIP. HotXLS previously had no case branch for this record so the entire payload was dropped on load; saving the workbook again therefore lost the header/footer image. The reader now preserves the full record body (every Continue chunk concatenated) per sheet, and the writer re-emits it in the spec-correct position so Excel reopens the file with the original images intact. - Reader concatenates all Continue chunks before storing the payload. The new
ParseHFPicturebranch inParseWorksheetRecordwalks every entry of the collatedDataList(the $086C body in slot 0 plus any number of $003C bodies that follow) and stores the joined bytes as a singleTByteson the worksheet. A sheet may carry several entries — up to six in Excel's UI (default / first / even pages × left / center / right) — so the storage is an array rather than a single field. - Writer emits HFPicture records right after PageSetup, then lets the existing Continue splitter handle the BLIP overflow. A new
StoreHFPicturesstep inserts itself into the per-sheet store sequence immediately afterStoreSetup, matching the*HFPictureposition in [MS-XLS] §2.1.7.20.2 worksheet ABNF. Each preserved body is wrapped in a fresh $086C header and routed throughAppendData— the existingAddContinuehelper takes over when the BLIP exceeds the 8224-byte BIFF limit, so a 30-50 KB JPEG ends up spread across one $086C plus four to six $003C records exactly the way Excel writes it. - No new public API surface. Wave G is a round-trip preservation feature; HFPicture content is opaque to application code (no caller-facing setter to inject a new header image). Workbooks that never carried an HFPicture record produce byte-identical output as before. The internal helper
TXLSWorksheet._AddHFPictureBlobis reserved for the reader; the underscore prefix flags it the same way as_Drawing/_Autofilter.
Version 2.56.0
- Chart backlog wave B: worksheet tab color round-trip (BIFF8 SheetExt). Sheet tab colors set in Excel via right-click Tab Color are now both readable and writable through the BIFF8 path.
TXLSWorksheet.TabColoris a new public property that takes a HotXLSLongWordcolor (low byte=R, mid=G, high=B, top byte=0, same convention asFont.Color/Interior.Color); the default value0means "automatic / no tab color" and suppresses the record entirely. - Writer emits a per-sheet
SheetExt($0862) record. One 48-byte record is appended to the workbook globals stream just before EOF for every sheet whoseTabColoris non-zero. Layout follows [MS-XLS] §2.4.282 SheetExt v1 + §2.5.108 FullColorExt: the 12-bytefrtRefHeaderU+ 4-bytecb+ 4-byte reservedflagsTab1+ 16-byte FullColorExt block (xclrType=2 RGB, nTintShade=0, xclrValue=tab color) + 4-byteiTabId(0-based stream order of the matching BoundSheet8) + 4-byte reservedflagsTab2.iTabIduses the same iteration order as the BoundSheet block so the two stay aligned across multi-sheet workbooks. xlExcel5 output is untouched (no SheetExt record in BIFF5). - Reader restores the tab color on open. A new
ParseSheetExtbranch inParseWorkbookRecord's case table readsiTabIdand the FullColorExt block and writes the resolved RGB value back toSheets[iTabId + 1].TabColor.xclrType=2(RGB) stores the raw value;xclrType=1(indexed icv) resolves through the workbook palette (Colors[icv, 0]) so the application sees a concrete RGB regardless of how Excel happened to encode it;xclrType=0(auto) andxclrType=3(theme) leaveTabColorat its default0, matching the writer-side "no tab color" semantics. - Round-trip example. Setting
Workbook.Sheets[1].TabColor := $00FF00(green: B=0, G=$FF, R=$00) before SaveAs to .xls produces a workbook that opens in Excel with a green tab; saving that same workbook back out and re-opening it returns the property to$00FF00. Files coming from Excel with one or more tab colors are now opened without losing the colors, instead of having every tab default to automatic.
Version 2.55.1
- Restore main-branch build. Two pre-existing identifier errors that left HotXLS unable to compile on RAD Studio 12+ are now fixed. Both came in with earlier bug-fix releases but had no compile gate, so the regressions only surfaced when the next contributor tried to rebuild the package.
- Shared-formula multi-column writer no longer references a missing record field. The v2.50.0 SHRFMLA wire-format fix added
Data.AddByte(Group.LastCol)inWriteShrfmlabut did not extendTXLSShrfmlaGroupwith the new field, so the package failed to compile with "Undeclared identifier: LastCol". The record now carriesLastColalongside the existingFirstCol, andCollectSharedFormulas.OpenGrouppopulates it with the group's anchor column. Behavior on single-column shared-formula groups (the only kind the collector currently produces) is unchanged; the spec-correctcolLastbyte is now real instead of an alias ofcolFirst. - XLSX cell writer references
XlsxFloatToStrthrough a forward declaration. The v2.51.0 locale-safety fix introduced an XlsxFloatToStr call in the cell-value branch (line 2718) but the helper is defined a few hundred lines below (line 2910). Without a forward declaration, Delphi's single-pass compiler reported "Undeclared identifier: XlsxFloatToStr". A forward declaration is now placed at the top oflxHandleX's implementation block so both the early call site and the existing definition compile cleanly.
Version 2.55.0
- Chart backlog wave A: multi-series cache wiring + chart / axis title captions. Two long-standing chart-builder limitations are now fixed; both come from the v2.54.0 audit backlog. The first emit-side change is wire-format only (no API change); the second wires through existing
AddChartSheetTitle / CatAxisTitle / ValAxisTitle parameters that previously had no effect. - Per-series values cache, no longer truncated to Series[0]. [MS-XLS] §2.1.7.20.1 chart-sheet ABNF allows
*(SIIndex *NUMBER)— oneSIIndex(1) + N×NUMBERblock per series. The Phase-1 builder emitted only one cache block sized bySeries[0].ValArea, so any later series with a longer value range had its cache slots silently cut short. The builder now (a) computes the maximum point count across all series for theDIMENSIONSrow range, and (b) loops over every prepared series emitting an individualSIIndex(1)block whose NUMBER count matches that series'AreaPointCount(ValArea). Cache slots stay zero-valued (BRAIPtgArea3dreferences still resolve to the live source cells at draw time); the change is purely about giving Excel's strict parser a complete cache footprint. - Chart Title / CatAxisTitle / ValAxisTitle captions are written through. The Phase-1 implementation accepted three Title parameters on
AddChartSheetoverloads and silently discarded them ("Junk := Length(...)" placeholder). They now emit a proper attached-label block —TEXT($1025) +POS+BRAI-literal-tStr +SeriesText($100D) +OBJECTLINK($1027) + End — at the spec-correct position just before theCHARTcontainer's End record per [MS-XLS] §2.1.7.20.1 CHARTTITLES ABNF.OBJECTLINK.wLinkObjuses 1 for chart title, 2 for value-axis title, 3 for category-axis title per [MS-XLS] §2.4.182. Callers passingTitle := 'Quarterly Sales'finally see "Quarterly Sales" rendered above the plot area in Excel.
Version 2.54.1
- [MS-XLSX] cross-audit follow-up. Two targeted fixes from the v2.52.0 audit’s "deferred" list. Both are user-observable; neither breaks existing call sites.
- Date cells keep their date format when combined with any single non-numFmt style. A
TDateTimecell withFontIndex/FillIndex/BorderIndex/AlignmentIndex/ProtectionIndexset but no explicitNumberFormatIndexused to route through a single-dim cellXf whosenumFmtIdwas 0 (General), so Excel rendered the cell as a raw serial number (e.g.46038) instead of1/15/2026. The XLSX style engine now treats the implicit date as a virtual dim: any other styled dim promotes the cell into a composite cellXf that carriesnumFmtId=14alongside the real dim. Pure-date cells with no other styling still route toXlsxXfIndexDateas before. New public surface:TXLSXWorkbook.RegisterCompositeXf/GetCompositeXfgain 7-arg overloads that accept an OOXML built-innumFmtIdoverride (the existing 6-arg signatures are unchanged and forward to the new variant with the override disabled). - Sheet names that violate Excel’s naming rules are auto-sanitised. Excel rejects sheet names that exceed 31 characters, contain
: \ / ? * [ ], start or end with a single quote, equal the reserved nameHistory(case-insensitive), or collide with another sheet name (case-insensitive). HotXLS used to pass the raw user input straight through, so calls likeWorkbook.Sheets.Add('Q1:Q2 Sales')produced a workbook that failed to open with the “The name you entered is not valid” dialog.TXLSXSheets.Addnow normalizes the name via the newXlsxSanitizeSheetNamehelper (forbidden chars become_, surrounding single quotes drop, length caps at 31, empty / reserved fall back to'Sheet') and appends a' (2)'/' (3)'suffix on case-insensitive collisions. Idempotent on already-valid names, so sheets parsed out of conforming .xlsx files round-trip unchanged.
Version 2.54.0
- Deep spec-audit round 2 against [MS-XLS] v20250520 / [MS-OFFCRYPTO] v20240820. Eight independent corner-case / wire-format defects found by eight parallel code-reviewer agents auditing Worksheet shell records, SST/Continue edges, formula PTG depth, encryption padding, chart records, drawing/comment writer, and Pivot/Table support. Public API unchanged; existing code recompiles untouched.
- Comment Note record now spec-conformant. Two distinct fixes in
TMsoDrawing.StoreNotes(per [MS-XLS] §2.4.179 Note): (1)idObjis now written as a Word (2 bytes) instead of a LongWord (4 bytes), correcting a 2-byte offset slide that misaligned every byte after idObj; (2) the previously absentstAuthorXLUnicodeString field is now serialized (cch + flags + UTF-16 body) fromFAuthorinstead of being replaced by a bareWord(0). Excel's strict reader terminates Note parsing atstAuthor, so omitting it corrupted comment metadata on round-trip even when Excel still opened the file. - Comment
Visibleproperty no longer inverts intent.TMSOShapeTextBox.GetVisible/SetVisiblereturned True whenfHiddenwas set and storedfHiddenwhen callers asked for the comment to be visible — the bit's meaning is the inverse ([MS-XLS] §2.4.179 grbit bit1: 1 = hidden). Callers usingComment.Visible := Truepreviously got hidden comments; now the property matches its name in both directions. - RC4 password derivation correct for 16-character or longer passwords. [MS-OFFCRYPTO] §2.3.6.1 stores the MD5 single-block message-length field as a 64-bit little-endian bit count in bytes 56–63. The previous reader (
lxDecryption) and writer (lxEncrypter) both wrote only the low byte viaSetByte, silently dropping the high byte for passwords of 16+ characters (length-in-bits ≥ 256). Long-password files now derive the same ValDigest on both sides; previously long passwords were rejected even when correct, and HotXLS-encrypted files with long passwords could not be re-opened. - Formula
tFuncVartoken now masks thefCeFuncbit out of the function-name lookup. [MS-XLS] §2.5.198.63 splits the trailing Word intotab(15 low)+fCeFunc(1 high); the reader stored the full Word intoItem.IntValueas the hash-table key, so functions emitted with the user-prompt flag (macro/UDF callers) hashed to$8000 | iftaband produced an empty function name, killing the entire formula. Now masked with$7FFF. - Multiple FILEPASS records are now ignored after the first. [MS-XLS] §2.4.117 allows at most one FILEPASS; a hostile or malformed Workbook with a second FILEPASS used to free the live decrypter and re-derive a fresh keystream mid-stream, putting it out of sync with the data already consumed. Subsequent FILEPASS records are now silently dropped while encryption stays armed.
- PtgArray V-class opcode emitted correctly.
GetArrayData(aClass=0)previously fell through to the A-class opcode ($60); per [MS-XLS] §2.5.198.32 the opcode is$20for V-class,$40for R-class,$60for A-class.aClass=0now correctly maps to$20. - Chart3DBarShape no longer emitted on 2D charts. [MS-XLS] §2.4.47 specifies that Chart3DBarShape "MUST be ignored if the current substream does not contain a Chart3d record". HotXLS doesn't yet support 3D chart types (no Chart3d record emitted), so writing an empty Chart3DBarShape inside every DataFormat block was both redundant and non-spec (the empty payload also failed the 2-byte riser+taper layout). The record is now omitted entirely.
- Audit backlog captured in
TechnicalNotes.md. Larger spec gaps surfaced by the round 2 audit (XFExt records not emitted, PtgArray constant data not serialized, Chart Title/CatAxisTitle/ValAxisTitle parameters dropped, per-series chart cache, Pivot Table / QueryTable / DConn unsupported) are documented as known gaps for future feature work rather than silent omissions.
Version 2.53.0
- OpenOffice excelfileformat.pdf cross-audit round 2. Three additional BIFF8 wire-format fixes uncovered by re-running the spec sweep after v2.50.0, this time targeting modules the first pass had touched only lightly: the formula token decoder, the XF alignment byte, and the ROW outline-collapse boundary check. Public API unchanged.
- Macro-command formulas with the user-prompt flag no longer corrupt the parser (§3.7.2 tFuncVar). The reader pulled the byte at offset+1 of a
tFuncVartoken straight into the argument count, but bits 6-0 ($7F) carry the argument count and bit 7 ($80) is the "user prompt for macro commands" flag. Any tFuncVar with the prompt bit set was read as 128 + N arguments, blowing the formula stack and yielding nil for the whole expression. The reader now masks with$7Fper spec. - JustifyLastLine alignment flag round-trips correctly (§5.115 XF). The XF BIFF8 body's alignment byte (offset 6) packs HorAlign + WrapText + VerAlign + JustifyLastLine. Previous code wrote three of those four fields but omitted bit 7 (
$80, JustifyLastLine). Cells withJustifyLastLine = Truesave with the bit cleared, so the distributed / justified last-line rendering reverts to the Excel default after a save / reopen. - Row 0 / row 65535 no longer mis-flagged as outline-group boundaries (§5.88 ROW). The fCollapsed (
$10) bit is set when a row's outline level differs from its neighbors. For the boundary rows the "neighbor" doesn't exist, butOutlineLevel[-1]andOutlineLevel[65536]returned the default level 1, which made every row 0 / row 65535 with an explicit outline level > 1 falsely register as a collapsed outline group edge. The check now treats absent neighbors as matching the row's own level so the transition fires only for genuine outline boundaries.
Version 2.52.0
- [MS-XLSX] cross-audit — round 2. Three follow-up refinements to the XLSX writer after the v2.51.0 audit. Each item is independently grounded in an ECMA-376 or W3C XML clause; combined effect is better OOXML consumer interop and quieter validation output.
- Cell strings with leading / trailing whitespace round-trip. The inline-string fallback in
XlsxFormatCellValueemitted<is><t>...</t></is>withoutxml:space="preserve"; XML 1.0 §3.3.3 normalization stripped the surrounding spaces on load, so a cell whose value was “ Pending ” came back as “Pending”. The SST, rich-text run, and comment-text paths already carried the attribute; the inline-string fallback now matches. - Every worksheet now carries a
<dimension>hint. ECMA-376 §18.3.1.35 marks the<dimension ref="..."/>element optional but every Excel-written .xlsx contains it; consumers that read it (Apache POI / NPOI / OOXML SDK) pre-size their cell buffers from the hint instead of growing dynamically. The writer now computes the bounding rectangle of populated cells during the row-grouping pass and emits the element as the second child of<worksheet>(after<sheetPr>if present, before<sheetViews>). Empty worksheets collapse to<dimension ref="A1"/>. <sst count="...">reports total cell references, not unique strings. ECMA-376 §18.4.8 definescountas the total reference count anduniqueCountas the distinct-string count; the writer used to emit both asFStrings.Count(always equal), misleading OOXML consumers about reference density.TXLSXSharedStringsnow tracks a separateRefCountthat increments on everyAdd/AddRichcall regardless of dedup, and the attribute reports the real reference total.
Version 2.51.0
- [MS-XLSX] cross-audit pass. Five wire-format and locale-safety fixes across the XLSX writer uncovered by validating against the Microsoft [MS-XLSX] — Excel (.xlsx) Extensions to the Office Open XML SpreadsheetML File Format documentation (v20260108) and the underlying XML 1.0 / OOXML schema requirements. Public API is unchanged; existing applications recompile and re-save without modification.
- Saved .xlsx files now open on every Windows region. Page margins, column widths, row heights, font sizes, color tints, theme tints, custom-font rich-text run sizes, and the
<v>-wrapped numeric value of every cell were being serialized through Delphi’s locale-awareFloatToStr. On Windows configured for a region whose decimal separator is ',' (most of continental Europe and parts of Asia / Latin America) the writer emitted attribute values likewidth="8,43"andtop="0,75", which the OOXML schema rejects — Excel surfaced “We found a problem with some content in <file>.xlsx” and offered a recovery pass that wiped page setup, column widths, and the numeric cell values. Every double-valued OOXML attribute now uses an invariant '.' separator regardless of the active region. The same fix also patches the in-memory column-width / row-height cache that previously stored locale-formatted strings;GetColWidth/GetRowHeightreturned0.0right after aSetColWidth/SetRowHeightcall on the same workbook in those regions. - Cells containing control characters no longer break the workbook. XML 1.0 §2.2 forbids ASCII codes 0x00–0x08, 0x0B, 0x0C, 0x0E–0x1F (and U+FFFE / U+FFFF) anywhere in an XML document — they cannot be encoded as numeric character references either. Cell values that happened to contain those bytes (legacy data feeds, log dumps, escape-key sentinels) used to be written into
<t>...</t>verbatim, producing an .xlsx Excel refuses to open with no path back to the data. The writer now silently strips the forbidden code points; valid whitespace (TAB / LF / CR) is preserved. - Newlines inside attribute values round-trip correctly. XML §3.3.3 normalizes raw TAB / LF / CR inside an attribute value to a single space before handing the value to the application. Multi-line headers / footers, defined-name comments, multi-line data-validation prompts, hyperlink display strings, and any other attribute that carried a newline used to collapse to a single space on load.
XlsxEscapeAttrnow emits those characters as	/
/
numeric character references so Excel reads them back as the original whitespace. - <col> entries emitted in ascending column order. ECMA-376 18.3.1.4 / 18.3.1.13 expects every
<col min="..."/>entry to appear in ascendingminorder. The previous TStringList(Sorted=True) dedup sorted the integer column indices lexically ("10" before "2"), so a sheet that customized both column 2 and column 10 produced<col min="10"/>before<col min="2"/>. Excel tolerated the layout but Office File Validation flagged the file and strict OOXML consumers (Apache POI strict mode, LibreOffice with strict-validation enabled) rejected it. Column entries are now sorted numerically before emission.
Version 2.50.0
- OpenOffice excelfileformat.pdf cross-audit pass. Eight follow-up wire-format and lifetime fixes uncovered by re-validating the BIFF8 writer / reader against the OpenOffice Microsoft Excel File Format documentation after the v2.49.0 [MS-XLS] pass. Public API is unchanged; existing applications recompile and re-save without modification.
- Shared formulas now span the full column range (§5.94 SHAREDFMLA). The
SHRFMLArecord body wrotecolFirstinto thecolLastbyte, collapsing every multi-column shared formula group to a single-column group in Excel. Fill-down formulas across two or more columns now expand to the correct cells when the workbook is reopened. The reader-sidecolLastwidth was also corrected (was reading a 16-bit value that pulled in the adjacent reserved byte). - Shared formula Boolean / Error results round-trip correctly (§5.50 FORMULA). The same
FormulaValuebyte-layout bug fixed in v2.49.0 for standalone formulas was still present in the shared-formula writer (cells that carry atExptoken pointing at aSHRFMLAmaster). CachedTRUE/FALSEand#REF!/#DIV/0!/etc. values now appear as written instead of always reading back asFALSE/#NULL!. - FORMULA option flags identify shared-formula members (§5.50 FORMULA). Cells inside a shared-formula group now set the
fShrFmlabit (0x0008) in the FORMULA record option flags so strict parsers recognize thetExptoken as a back-reference instead of attempting to evaluate it as an opaque opcode. - Bold and underlined fonts identified correctly by strict readers (§5.45 FONT). The FONT option-flags word now sets bit 0 (bold) and bit 2 (underlined) in addition to the dedicated
bls/ulsfields. LibreOffice import paths and Office File Validation that check the option flags rather than the standalone fields no longer mis-classify bold or underlined fonts as plain. - CFRULE option flags carry spec-mandated constant bits (§5.16 CFRULE). When at least one formatting block is present the option-flags DWORD now ORs in bit 7 (
0x00000080, "Always 1") and bits 21-19 (0x00380000, "Always 111"). Conditional-formatting rules survive a save / load round-trip on strict CFRULE parsers; the "no format blocks" special case (all-zero option flags) is preserved. - CHOOSE() formulas read back correctly (§3.10.5 tAttrChoose). The
tAttrparser previously skipped a fixed 4 bytes for every subtype;tAttrChoose($04) is variable-length (header + per-choice jump table + error jump), so the decoder walked into the jump-table contents and corrupted every following token. The decoder now readsncand advances by6 + 2·ncbytes, matching the spec layout. - Data-validation and conditional-formatting teardown no longer dereferences past the tail. Two
Destroydestructors (TDataValidator,TCondFormatter) walked0..cnton lists whose valid indices are0..cnt-1, accessing one element past the end during workbook teardown. Long-running applications that build and discard many CF / DV containers no longer occasionally AV at shutdown.
Version 2.49.0
- Spec-audit pass against [MS-XLS] v20250520. Sixteen wire-format and behavior fixes across the BIFF8 writer, formula decoder, encryption path, chart builder, conditional-format and data-validation runtime, SST/ExtSST index, VBA storage reader and OLE stream open path. Public API unchanged; existing code recompiles untouched. Highlights below.
- BIFF8
Formularecord Boolean and Error results now round-trip correctly. [MS-XLS] §2.5.133FormulaValuepacks the type tag into byte[0] and the bool/error code into byte[1]; the writer used to emit$0001/$0002as a Word and put the value into byte[2], so cached Boolean formulas read back as False and Error formulas as #NULL! in every consuming application. - Cell colors set via
SetColorRGBrender correctly. The color manager wrote ColorType=1 (theme) for RGB values and ColorType=2 (RGB) for theme values, swapping the two code paths. Both setters now route through the matching ColorType bucket so RGB values feed the RGB resolver and theme indices feed the theme resolver. - XOR Obfuscation and RC4 decryption fixed. The XOR Method-1 decrypt loop now matches [MS-OFFCRYPTO] §2.3.7.3 (XOR first, then rotate-right 5; seed XorArray index from the entry stream position, not the post-process position). RC4
Skipnow walks every 1024-byte boundary continuously instead of jumping straight to the destination block, keeping the keystream state aligned with the file position for every subsequentDecryptcall. - Chart sheet wire format aligned with the spec. SIIndex records emit in numIndex=1 → 2 → 3 order ([MS-XLS] §2.4.262; was 2 → 1 → 3, which strict parsers reject), DEFAULTTEXT count clamped to the spec maximum of 2 ([MS-XLS] §2.1.7.20.1; was 4), Chart3DBarShape placeholder uses the correct id
$105F(was$1066). - HLink tooltip records load correctly. The
HLinkTooltip($0800) body decoder used to read FrtRefHeader anchor fields at offsets 2/4/6/8 instead of 4/6/8/10, so the anchor-range comparison never matched and per-cell tooltips were silently discarded; tooltips now associate with their hyperlinks. BIFF8 range-reference decoder also fixed aFFirstColRel/FLastColReltypo in the BIFF5/7 fallback sign-extension path that mis-decoded ranges with negative last-column relative offsets. - Conditional formatting and data validation runtime fixes.
IsContainRowin bothTCondRangeandTDVRangereturned true only when the range degenerated to a single row (the comparison was inverted); per-row clearing of CF / DV rules now works on multi-row ranges. Three destructors (CondFormat, formatter, DVRangeList) usedfor i := 0 to cnton lists whose valid indices are 0..cnt-1; the off-by-one is fixed and long-running workloads no longer occasionally AV during teardown. CF12 icon-set rules emit ct=$06(IconSet per [MS-XLS] §2.4.43) instead of ct=$05(Filter), so workbooks containing icon-set rules open with the rules intact. - ExtSST quick-find index spec-conformant for large workbooks. The
dsstfield is now computed asmax((cstUnique / 128) + 1, 8)per [MS-XLS] §2.4.107 instead of the static default of 8; workbooks with more than ~900 unique strings now build a correctrgISSTInfbucket array and Excel's quick-find SST lookup works. - WriteAccess record honors [MS-XLS] §2.4.349 cch <= 54. The
userName.cchfield now carries the actual user-name length (6 for "HotXLS") with the remaining bytes filled by the spec-defined "unused" trailer. Previously the field carried cch=109, which Office File Validation flags as a hard format violation. - BIFF8 Workbook stream open path matches the spec spelling. The first OLE
OpenStreamattempt uses 'Workbook' (capital W per [MS-XLS] §2.1.7.20) instead of all-lowercase, so compound-file containers on case-sensitive OLE implementations open the stream on the first try. - VBA module reader no longer drops the last byte of each stream. The seek-to-end length used
trunc(l) - 1instead oftrunc(l), truncating the compressed-source tail and causing the RLE container decompressor to terminate early on certain modules.
Version 2.48.10
- HotXLS-saved .xls files now include the two OLE compound-file property-set streams Excel expects on every workbook:
\005SummaryInformationand\005DocumentSummaryInformation. Pre-v2.48.10 only TRIAL builds wrote them (as a banner-stamping mechanism); release builds produced .xls files whose Workbook stream was valid BIFF8 but whose OLE container was missing the standard Office metadata streams. Excel 2003's strict parser treats this as the "File error: data may have been lost" condition (modern Excel keeps the Protected View "this file has problems" banner). Adding the streams resolves the dialog on chart workbooks without changing any BIFF8 record. - The two streams are emitted via Win32's
StgCreatePropSetStg+IPropertySetStorage.Create+IPropertyStorage.WriteMultiplepath — the same code Microsoft Office uses, so Office File Validation accepts the byte layout. The SummaryInformation stream is created with defaultAppName="HotXLS"and empty Author; TRIAL builds still overwrite the Author / AppName values with the trial banner vialxTrial.XlsTrialStampXlsSummaryafter the default stamp, so trial files remain identifiable in Excel's File > Info > Properties pane. The DocumentSummaryInformation stream is created empty (Win32 still emits the property-set header + CODEPAGE property — enough for Excel's strict parser to consider the metadata "present and well-formed"). - This fix is universal — every HotXLS-saved .xls now carries the standard OLE metadata, not just chart workbooks. Worksheet-only outputs from earlier releases happened to escape the symptom because Excel is more lenient with metadata-less worksheet workbooks than with chart-sheet workbooks, but they were also technically missing the streams. New
Lib/lxXlsSummary.pasunit holds the two helper procedures; lxHandle.pas calls them unconditionally insideTXLSWorkbook.SaveWorkbookafterStoreWorkbookpopulates the Workbook stream and beforeFDocStorage.Commit. - This release closes the eight-version chart-Phase-2 follow-up sequence (v2.47.0 — v2.48.10): from
tArea3Dseries binding, through BRAI id realignment, sdtX/cValx Excel-observed values, SERIESTEXT placement, Workbook stream padding removal, sheet-level shell records, and now OLE metadata streams. The result is a chart .xls that opens in Excel 2003 and modern Excel without any "file has problems" warnings, byte-aligned with what Excel itself writes for the same data.
Version 2.48.9
- BIFF8 chart sheets now carry the full set of sheet-level shell records that Excel 2003+ requires. v2.48.8's chart_phase2_demo.xls rendered the column chart correctly but Excel 2003 still surfaced a "File error: data may have been lost" dialog (newer Excel versions kept the Protected View banner) because the chart substream was missing the same sheet-level records a worksheet substream carries. Real Excel files (both from-scratch and re-saved variants of HotXLS output) wrap the chart records in this shell:
- Before the chart records (between BOF and UNITS):
HEADER($0014, empty),FOOTER($0015, empty),HCENTER($0083, value 0),VCENTER($0084, value 0),SETUP($00A1, 34-byte page setup),PRINTSIZE($0033, value 3). - After the chart records (after the outermost CHART END, before EOF):
DIMENSIONS($0200, 14 bytes) declaring the cached data range, threeSERIESINDEX($1065) markers for categories / values / bubble-size cache slots, aNUMBER($0203, 14 bytes) per cached value point, andWINDOW2($023E, 10-byte chart-sheet variant) at the end.
dev-notes/biffview-refs/excel_column_chart_resaved.xls(the Excel-saved column-chart reference, kept local). - Before the chart records (between BOF and UNITS):
- The values cache currently emits zero placeholders (
0.0for every data point), matching what Excel itself writes when re-saving a chart whose source cache wasn't pre-populated. The chart still plots the real data because the BRAIPtgArea3dreferences resolve to the source worksheet cells at draw time — the cache is what lets Excel parse the chart sheet without warnings, not what feeds the plot. A future release can populate the cache with the actualValuesrange numbers; the wire format is in place. - End-to-end smoke test: open the regenerated
Demo/Delphi/XlsCreateChart/chart_phase2_demo.xlsin Excel 2003 — the "File error: data may have been lost" prompt no longer appears, and the column chart renders identically to before. The same file opens cleanly in modern Excel without the Protected View banner.
Version 2.48.8
- BIFF8 (.xls) writer no longer pads the Workbook stream with trailing zero bytes.
TXLSWorkbook.StoreOleFileused to callStoreExtraSpace(Book)after the last sheet's records, padding the stream length up to the next 512-byte (OLE sector) boundary. Excel's strict parser treats those trailing zero bytes as a bogus stream of0x0000-id records and surfaces a "this file has problems" Protected-View prompt — visible on the v2.48.7 chart_phase2_demo.xls even after the BRAI / sdtX / SERIESTEXT wire format was fixed and the chart itself rendered correctly. Removing the call drops the padding from the Workbook stream content; OLE sector padding still happens, but in the CFB container layer below the stream where Excel itself puts it (every Excel-saved .xls has a non-sector-aligned Workbook stream length). - Symptom history: any HotXLS-saved .xls whose total valid-record byte length didn't already happen to land on a 512-byte boundary carried the trailing-zero stream. The bug existed from the repository bootstrap commit forward but rarely surfaced — most workbooks happen to have enough records that the padding ended up small (≤ 4 zero bytes that Excel tolerated). Chart-sheet workbooks happen to land far short of a sector boundary (the v2.47.0 — v2.48.7 demo padded 420 zero bytes), so this is where the Protected-View prompt became consistent.
StoreExtraSpacethe method survives as private dead code inlxHandle.pasin case a future code path legitimately needs sector-rounded streams; the call site inStoreOleFileis removed. Workbook stream length now equals exactly the byte length of all valid records (chart demo: 3164 bytes; Excel from-scratch reference: 4419 bytes — both non-sector-aligned). Reader code paths are unaffected — HotXLS has always read non-sector-aligned Workbook streams from Excel-saved files correctly.
Version 2.48.7
- BIFF8 chart series records finally render correctly in Excel. The fix is realigning BRAI id assignments with [MS-XLS] §2.4.51 — the parent implementation plan documented them inverted, propagating the bug through every chart release from v2.41.0 to v2.48.6. The correct id semantics are:
id = 0— series name / legend label (plan said: categories)id = 1— values (correct)id = 2— categories (plan said: name)id = 3— bubble sizes (plan said: errBars)
- Also realigned to match Excel-saved column-chart wire format:
SERIES.sdtXis now0x0003(Excel's observed value for text categories, not the [MS-XLS] §2.4.252 documented0x0001), andcValxcarries the actual category count instead of0. The "cValx MUST = 0whensdtX∈ {1, 3}" spec rule is contradicted by every Excel-saved sample examined. - The
SERIESTEXT($100D) literal-name carrier returns, but in the right place: immediately after theid = 0BRAI, still inside the SERIES BEGIN/END block. This is what real Excel files do — the v2.48.5 emit point (after the whole BRAI cluster, beforeDataFormat) was wrong and broke chart parsing; the correct position is right next to the series-name BRAI. - v2.41.0 — v2.48.6 saved chart files all carry the inverted-BRAI wire format. They open in Excel without error but show the legend / X-axis symptoms above. Re-save through v2.48.7+ to migrate; no API change — the same
AddChartSheetcall now produces a chart that renders identically to what Excel itself would write for the same data. - New byte-level inspection tool
dev-notes/biffview-refs/dump_chart_substream.pydumps the chart substream of any BIFF8 .xls with per-record decoding (SERIES / BRAI / SERIESTEXT / AXIS / CATSERRANGE / LEGEND / OBJECTLINK / POS / TEXT). Used during this release to byte-diff HotXLS output against the Excel-saved reference samples; available for future chart-spec follow-ups.
Version 2.48.6
- BIFF8 chart-sheet substream retracts the v2.48.5 changes. Excel testing on the regenerated v2.48.5 demo did not render the chart at all — worse than the v2.47.0 baseline (where the chart drew but with wrong X-axis labels). The two v2.48.5 changes were the cause:
SERIES.sdtX = 4is not actually accepted by Excel even though Apache POI exposesCATEGORY_DATA_TYPE_TEXT = 4. [MS-XLS] §2.4.252 strictly requiressdtXto be0x0001or0x0003.- The added
SERIESTEXT($100D) record inside the SERIES BEGIN/END block is a structural violation —SERIESTEXTbelongs in TEXT blocks (chart title / axis title / data label contexts), not inside the SERIES block. Embedding it there confused Excel's chart parser into rejecting the whole substream.
- v2.48.6 restores the v2.47.0 wire format AND adds one spec-compliance fix:
SERIES.cValxis now0whensdtX = 1([MS-XLS] §2.4.252 spec MUST rule). Pre-v2.48.6 wrotecValx = count(4 in the demo) which violated the rule and may have contributed to Excel's "document has issues" Protected-View prompt.cValystill carries the actual value count (sdtY=1 has no zero-rule). - New XlsCreateChart Delphi demo under
Demo/Delphi/XlsCreateChart/creates a two-sheet workbook: a "Data" worksheet with four (Region, Sales) rows and a "Chart1" column-chart sheet whose series bindsData!$A$2:$A$5/Data!$B$2:$B$5as Categories / Values. The demo is the permanent successor to the ad-hocchart_phase2_demo.xlsthat v2.47.0 — v2.48.5 placed in the repo root for BiffView testing; the root file has been removed, and the new demo writes its output alongside its own .exe. - Known unresolved: with v2.48.6's wire format, Excel still renders the column chart's X-axis labels as zeroes and falls back to concatenating category cell text for the legend (instead of using the BRAI id=2 PTG tStr literal
'Q1 Sales'). This is a real wire-format gap that needs an Excel-saved reference column chart to diff against — tracked as a follow-up. The 4-bar plot area itself renders correctly (heights 100/150/200/175 match Values).
Version 2.48.5
- BIFF8 chart series records now render correctly in Excel. v2.47.0 added
tArea3Dbinding for Categories / Values ranges, but Excel still drew the wrong X-axis labels (every category became0) and used the concatenated category text ("North South East West") as both legend label and auto-title instead of the user-supplied series name. Two fixes restore the intended rendering: - The
SERIESrecord'ssdtXfield is now4(text categories), not1. [MS-XLS] §2.4.252 documents only1(dates) and3(BIFF7 sequence) but real Excel-saved column / bar / line chart files write4for text-axis categories (matching Apache POI'sCATEGORY_DATA_TYPE_TEXTconstant). WithsdtX=1Excel tried to coerce'North'/'South'cells to numbers, so the X axis showed four zeroes;sdtX=4lets the labels through verbatim. The pre-v2.48.5 wire form also violated the spec rule thatcValxmust be zero whensdtX=1. - A
SERIESTEXT($100D) record is now emitted inside the SERIES BEGIN/END block (after the BRAI cluster, beforeDataFormat) whenever a literal series Name is supplied. The Phase 2 first-half mechanism (BRAI id=2 + PTGtStr) is kept for completeness but Excel actually surfaces the legend label and the auto-title fromSERIESTEXT; with only the BRAI variant present, Excel fell back to concatenating category cells. After this fix the legend shows the real Name (e.g.'Q1 Sales') and an empty chart title leaves the auto-title slot blank rather than dragging category text into it. - Migration: v2.42.0 — v2.47.0 chart-sheet outputs still open in Excel without error but rendered the symptoms above. Re-save through v2.48.5+ to get the corrected wire format; no API change is required, the same
AddChartSheetcall now produces the correct chart.
Version 2.48.4
- BIFF8 CFEX ($087B) emit is retired. HotXLS no longer writes the paired CFEX after every CF12 ($087A) record — all Data Bar / Color Scale / Icon Set configuration now travels inline in the CF12 kind tail (v2.48.1 — v2.48.3). HotXLS-saved .xls files become smaller and exactly match Excel's own output (Excel-saved samples like
poi_ConditionalFormattingSamples.xlscontain zero CFEX records). Apache POI / NPOI already ignore CFEX, so this is a pure simplification with no readability loss. - BIFF8 CF12 read path now consumes the kind tail directly from CF12 instead of falling back to CFEX.
ParseCfExbecomes a no-op; the lxRead dispatch entry for $087B stays so HotXLS-saved v2.34..v2.47 files don't crash, but their CFEX content is ignored (those files also have a pre-v2.48.0 CF12 header layout and would not round-trip correctly — re-save through Excel or HotXLS v2.48.x to migrate). - This completes the 5-PATCH Gap #6 wire-format rewrite (v2.48.0 header + v2.48.1 data_bar + v2.48.2 color_gradient + v2.48.3 multistate + v2.48.4 CFEX retire). HotXLS-saved Excel 2007+ conditional formatting records now match Excel's own wire format byte-for-byte (modulo formula content), making them interoperable with Apache POI / NPOI and any other spec-compliant BIFF8 reader.
Version 2.48.3
- BIFF8 CF12 ($087A) Icon Set rules now emit the NPOI-compatible
multistatesub-record inline so Apache POI / NPOI can read the icon family identity, per-stop thresholds, and reverse / show-icon-only flags directly from CF12 without consulting the legacy CFEX record. Layout matches NPOIIconMultiStateFormatting.Serialize():reserved(2) + reserved(1) + num(1) + iconSet.id(1) + options(1)+ per-stopIconMultiStateThreshold(cfvo + equals byte + 4 reserved). - HotXLS
TXLSIconSetTypevalues0..16map 1:1 onto NPOI'sIconSetid enum (3Arrows / 3TrafficLights1 / 4RedToBlack / 5Quarters etc), so existing user code using icon-set constants continues to round-trip unchanged.ShowOnlysets options bit 0 (iconOnly),Reversesets bit 2. - Default per-stop thresholds (HotXLS
cfvPercentwith even-split values: 3-icon=0/33/67, 4-icon=0/25/50/75, 5-icon=0/20/40/60/80) emit as NPOI RangeTypePERCENT(4)+ value Double + equals byte (alwaysEQUALS_INCLUDE=1for now). This completes the kind-tail leg of the Gap #6 CF12 rewrite; CFEX emit retirement follows in v2.48.4.
Version 2.48.2
- BIFF8 CF12 ($087A) 2-color and 3-color Color Scale rules now emit the NPOI-compatible
color_gradientsub-record inline so Apache POI / NPOI can read the per-stop threshold + color + interpolation positions directly from CF12 without consulting the legacy CFEX record. Layout matches NPOIColorGradientFormatting.Serialize():reserved(2) + reserved(1) + numI(1) + numG(1) + options(1)+ per-stopColorGradientThreshold(cfvo + position Double) + per-stopstep + ExtendedColor. - Stops use NPOI's
step = 1 / (N-1)formula so a 3-stop Color Scale gets positions 0.0 / 0.5 / 1.0 and a 2-stop Color Scale gets 0.0 / 1.0. Per-stop colors (RGB or theme viaSetThemeColor) flow through the same ExtendedColor block used by Data Bars in v2.48.1. - Default kinds for 3-stop ColorScale (cfvMinOfRange / cfvPercentile=50 / cfvMaxOfRange) map to NPOI RangeType
MIN(2) / PERCENTILE(5) / MAX(3); the middle stop's value (50.0 as Double) and per-stop position are byte-verified againstpoi_ConditionalFormattingSamples.xlsrecord 215 in the test suite.
Version 2.48.1
- BIFF8 CF12 ($087A) Data Bar rules now emit the NPOI-compatible
data_barsub-record inline (28 bytes appended aftertemplate_params) so Apache POI / NPOI can decode the bar color, min/max thresholds, and percent length bounds without consulting the legacy CFEX record (which POI ignores entirely). Layout matches NPOIDataBarFormatting.Serialize():reserved1(2)+reserved2(1)+options(1)+percentMin(1)+percentMax(1)+ ExtendedColor(16) + ThresholdMin/Max. - HotXLS bar color (BGR
LongWord) is written into the ExtendedColor block withtype=RGB; theme-color Data Bars (set viaSetThemeColor) emittype=THEMED+ themeIndex + tint.MinLength/MaxLengthdefault 10% / 90% (configurable via the spec object), matching Excel's standard Data Bar appearance. - Color Scale and Icon Set kind tails (v2.48.2 — v2.48.3) and CFEX retirement (v2.48.4) are still pending; this release only completes the Data Bar leg of the Gap #6 CF12 rewrite. See the v2.48.0 entry for the header rewrite that this PATCH builds on.
Version 2.48.0
- BIFF8 CF12 ($087A) record header layout corrected against NPOI
CFRule12Record.serialize()and Excel-saved reference files. The previous layout placedipriority/iTemplate/grbit/parmsimmediately aftercce2and treatedcbdxfas a 2-byte length field; Excel actually writesext_formatting_length(4)+ an optionalSerializeFormattingBlockheader before the formula tokens, followed byformula_scale,ext_opts,priority,template_type, and a length-prefixedtemplate_paramsblock. - HotXLS-emitted CF12 records now match the byte layout of
poi_ConditionalFormattingSamples.xlsrecords 152..215 in the header section (data_bar / color_gradient / multistate kind tails arrive in v2.48.1 — v2.48.3; the paired CFEX still emits in this release and retires in v2.48.4). See the v2.47.1 entry for the CONDFMT12 sibling fix that motivated this rewrite. - The bogus 2-byte
nIDfield HotXLS appended at the end of every CF12 payload (no such field exists in the spec) is removed — rule identity is carried by the parent CONDFMT12 record. Existing v2.34.0 — v2.47.1 .xls files should be re-saved through v2.48.0 to rewrite their CF12 records.
Version 2.47.1
- BIFF8 CONDFMT12 ($0879) record wire format corrected to match Excel-saved files. The previous implementation followed an early draft layout that placed the 8-byte associated range as "reserved" inside the FtrHeader and inserted a non-existent 4-byte Reserved field before
numCF; Excel-saved .xls files actually fill the 8 bytes inside FtrHeader with the rule's associated cell range and have no Reserved field, so HotXLS-emitted files were shifted by 4 bytes plus had a zeroed range slot. Reference dump frompoi_ConditionalFormattingSamples.xls(Apache POI test fixture) record 152 now matches HotXLS output byte-for-byte. - Concrete fix:
StoreCondFmt12Headernow writes FtrHeader asrt(2) + grbitFrt(2) + associatedRange(8), then the body asnumCF(2) + needRecalcAndId(2, bits packed) + enclosingRange(8) + cref(2) + sqref array. Total fixed bytes drop from 30 to 26 (Reserved 4 removed).ParseCondFmt12updated symmetrically to read at the corrected offsets. - Existing HotXLS-generated .xls files written by v2.34.0 — v2.47.0 should be re-saved through v2.47.1 to repair their CONDFMT12 records. Files round-tripped via Excel between version bumps were never affected because Excel rewrote the records to its own correct layout on save.
Version 2.47.0
- BIFF8 chart-sheet creation now binds real data ranges to series. The
CategoriesandValuesA1-range strings supplied to the 6-argumentAddChartSheetoverload (e.g.'Data!$A$2:$A$5') are compiled intotArea3DPTG tokens written into the seriesBRAI id=0(categories) andBRAI id=1(values) records. Excel-opened .xls files now render actual data points in the plot area instead of the empty placeholder chart that v2.42.0 produced. - Sheet name resolution + EXTERNSHEET registration is automatic: the parser looks the named sheet up in the workbook, registers an XTI entry if one is not already present, and writes the matching 0-based
ixtiinto the PtgArea3d body. Re-saved files keep theixticonsistent with the workbook's final EXTERNSHEET ordering because the chart record stream is re-serialized through the formula compiler at save time. - Accepted range shapes:
Sheet1!$A$2:$A$5(absolute),Sheet1!A2:A5(relative markers tolerated — chart series always store as absolute),'My Sheet'!$A$1:$B$5(single-quoted sheet names with spaces), and single-cell refs likeSheet1!$A$1(degenerates to a 1×1 PtgArea3d). Series data-point counts (cValx/cValy) are now derived automatically from the area dimensions instead of staying at zero. - Unsupported reference shapes silently fall back to the v2.41.0-style
cce=0placeholder BRAI so the chart still opens in Excel: cross-workbook refs ([Book2.xls]Sheet1!...), named ranges (Sheet1!ProfitColumn), refs missing the!sheet prefix, and refs that point at the chart sheet itself. These deferred cases are tracked for follow-up phases. - This completes Roadmap #10 Phase 2 (column chart end-to-end). Series labels (Phase 2 first half, v2.42.0) plus series range binding (this release) means a column chart created via
AddChartSheetcan now display proper legend entries plus real plotted data without any post-processing in Excel.
Version 2.46.0
- The Differential XF (DXF) decoder on the BIFF8 side now resolves xclrType=1 (indexed palette) colors from the standard Excel 2003 64-color palette. Previously indexed colors fell through to the RGB path and the icv byte was misinterpreted as part of an RGB value, producing nonsense colors (typically dark gray) on read; Excel-saved .xls files that used indexed colors (rather than RGB or theme) for their CF12 DXF blocks now round-trip with sensible visual colors.
- The lookup uses the built-in Excel 2003 default palette baked into HotXLS — icv 0..7 are the eight system colors (black / white / red / green / blue / yellow / magenta / cyan), icv 8..15 mirror the system colors, and icv 16..63 are the standard customizable defaults (dark variants, light tints, browns, oranges, plums, etc.). Out-of-range icv values fall back to black ($000000).
- BIFF8 PALETTE record (rt=$0092) workbook customization is not consulted — a workbook that customizes its user-changeable palette slots (8..63) will have its CF12 DXF indexed colors resolved against the default values rather than the customized ones. For most Excel files that use the default palette this is correct; for files with custom palettes the visual color may shift slightly until full PALETTE plumbing lands in a future release.
- xclrType=0 (auto) and xclrType=2 (RGB) paths are unchanged — auto still becomes RGB 0 (black), RGB still decodes the xclrValue 4-byte BGR LongWord directly. Only the previously-broken xclrType=1 path is fixed.
Version 2.45.0
- Conditional formatting rules now round-trip the priority attribute on both the XLSX
<cfRule priority="N"/>and the BIFF8 CF12ipriorityfield. Previously every rule was assigned a sequential priority based on its index in the collection on save, and the priority on Excel-saved files was ignored on read; now user-set priorities survive a load-edit-save cycle on both backends. - New
Priorityproperty onTCondFormatRule(BIFF8 side) andTXLSXConditionalFormat(XLSX side). Default is zero, which preserves the existing auto-by-index behavior on save; set to a positive value to pin the priority and override the automatic assignment. - Priority lets multi-rule layering be controlled — when several rules cover the same range (e.g. a Data Bar + a CellIs highlight + an Icon Set), Excel applies them in priority order (lower number first). Round-tripping the priority means HotXLS-edited workbooks keep the user's intended rendering order instead of having it recomputed from collection iteration order.
- Default behavior unchanged for callers that don't touch
Priority: the XLSX writer continues to emitpriority="1","2","3"… in collection order, and the BIFF8 CF12 writer continues to emitipriority=1. Read paths populatePriorityfrom the wire value when present; explicit checks against zero remain backward-compatible with code that ignored the property.
Version 2.44.0
- Icon Set conditional-format rules now support per-stop icon overrides on the XLSX side. Each of the 3 / 4 / 5 stops in an icon set can opt out of the family default and display any icon from any of the 17 built-in Excel 2007 icon families, identified by icon set name + icon id. The OOXML element involved is
<cfIcon iconSet="OtherSet" iconId="N"/>emitted per overridden stop inside the<iconSet>body. - New methods on
TXLSIconSetSpecopt a stop into override mode:SetIconOverride(stopIndex, OverrideSet, IconId)andClearIconOverride(stopIndex). Read-only propertiesHasIconOverride[i],IconOverrideSet[i],IconOverrideId[i]expose the per-stop override state for inspection and round-trip preservation. - The XLSX writer emits
<cfIcon>children only for stops that calledSetIconOverride; stops without an override stay on the family default (no<cfIcon>emitted). The reader parses incoming<cfIcon>elements positionally inside an iconSet body and applies them to the matching stop on the newTXLSXConditionalFormat.IconSetspec. - Known limitations: BIFF8 (.xls) CFEX wire format has no slot for per-stop icon overrides, so writing an icon-set rule with overrides to .xls renders the family default for all stops (BIFF8 readers will not see the override info). Workbook-level icon-family validation is not enforced — passing an icon id beyond the override family's icon count is preserved verbatim but Excel may fall back to a default icon at render time.
Version 2.43.0
- XLSX (.xlsx) conditional-format rules now round-trip theme color references in the
<color>element of Data Bar and Color Scale rules. Previously the writer only emitted<color rgb="FFRRGGBB"/>and the reader ignoredtheme/tintattributes; now both sides preserve the theme palette index plus the optional tint, so styles like "accent-2 lightened 50%" survive an Excel open / HotXLS edit / Excel re-open cycle without being flattened to a frozen RGB. - New methods on
TXLSDataBarSpecandTXLSCfValue(used by ColorScale stops) opt the slot into theme mode:SetThemeColor(themeId, tint)andClearThemeColor. Read-only propertiesIsThemeColor,ThemeColorId,ThemeColorTintexpose the active mode. Tint is a single in the [-1.0, +1.0] range — negative values darken the theme color, positive values lighten it. - The XLSX writer emits
<color theme="N"/>whenTintis exactly 0.0, or<color theme="N" tint="0.5"/>when non-zero, matching Excel's own "shortest form" output. The reader parses both attribute combinations and falls back to the existingrgb=path when neitherthemenortintis present. - Default behavior is unchanged: rules created with the existing RGB-only
AddCondFormatDataBar/AddCondFormatColorScale*entry points keep emitting<color rgb="..."/>exactly as before. Theme mode is purely opt-in via the newSetThemeColorsetter on the returned spec object. - Known limitations: BIFF8 (.xls) CFEX still only carries the RGB color (single 4-byte BGR LongWord per stop / per data bar) — theme color in CFEX would need a parallel wire-format extension. Workbook theme palette resolution (theme1.xml parsing) is still out of scope; HotXLS preserves the theme index verbatim but does not resolve it to a concrete RGB for rendering / preview purposes.
iconSetrules carry no<color>elements so the theme-mode addition does not affect them.
Version 2.42.0
- BIFF8 chart-sheet creation API gains a fifth
AddChartSheetoverload that accepts an array ofTXLSChartSeriesInforecords (Name,Categories,Values). Each non-emptyNameis written into the chart substream as a literal series label via atStrPTG token on the series-nameBRAI(id=2). Excel uses this label in the legend and in the series selector, so a multi-series chart now ships with proper series captions instead of the default "Series1 / Series2" placeholders. TXLSChartSeriesInfois re-exported fromlxHandlealongsideTXLSChartType, so callers can build the array without importinglxChartBuilderdirectly.- This is the first half of Roadmap #10 Phase 2 (column chart end to end). The series-name path is wired up; category and value data binding (turning the
CategoriesandValuesA1-range strings into realtArea3Dcell-range references inside the BRAI) is still a placeholder — those two fields are accepted on the API but not yet emitted into the chart substream. A follow-up release will route them through the formula compiler and the EXTERNSHEET reference table to land the second half of Phase 2.
Version 2.41.0
- HotXLS gains programmatic BIFF8 chart-sheet creation. A new
AddChartSheetoverload set onSheetswrites a complete chart substream into the saved .xls file, where previously chart sheets could only be preserved opaquely on a load-save round-trip. Four overloads cover the common entry points: name only / name + chart type / + title / + category-axis title + value-axis title. The returnedIXLSWorksheetappears in theSheetscollection, gets a chart-sheet boundsheet entry written on save, and Excel opens it as a chart tab. - A new
TXLSChartTypeenum selects the chart kind:xlsChartTypeColumn,xlsChartTypeBar,xlsChartTypeLine,xlsChartTypePie. The enum is re-exported fromlxHandleso user code does not need to import the chart-builder unit directly. - The emitted chart substream covers the BIFF8 framing the spec calls for — BOF (dt=$0020), Chart / PlotGrowth / Frame / Series / SheetProperties / AxesUsed / AxisParent (POS + category axis + value axis + PlotArea + Frame + ChartFormat + chart-kind record + ChartFormatLink + Legend), EOF — so Excel recognizes the result as a real chart tab of the requested kind. Existing chart-sheet round-trip via
TXLSCustomChartis unchanged. - This is the first cut of Roadmap #10 (CHART substream — the largest item in the BIFF8 audit). Phase 1 ships the framing and the API surface so that subsequent phases can layer in series data binding, axis titles, and richer per-chart-type formatting.
- Limitations: the new chart sheet renders an empty default chart of the requested kind. Series data binding (linking the chart to actual cell ranges), populated title text, and per-chart-type formatting (gap width / bar overlap / pie hole size / marker style) are deferred to subsequent phases. Save-and-reopen with Excel preserves the chart sheet's presence and its kind, but not yet user-supplied data.
Version 2.40.0
- New XlsCondFormat12 Delphi demo under
Demo/Delphi/XlsCondFormat12/demonstrates the v2.34.0+ conditional-format extension API end to end. Three buttons let you generateCondFormat12.xls(BIFF8 backend),CondFormat12.xlsx(XLSX backend), or both side by side. Each output sheet carries the same four rule kinds — Data Bar, 3-color scale, Icon Set (3 arrows), and 2-color scale — covering ten data rows so you can open the files in Excel and visually verify how each backend renders the extension rules. - The BIFF8 path additionally exercises the v2.35.0 DXF Style override: the Data Bar rule attaches white bold font color so the cell text stays legible over the dark bar fill.
- New DUnitX test fixture at
Tests/Delphi/HotXLS.CondFormat12Tests.pascovers ten round-trip and API-surface scenarios for the CF12 family: BIFF8 round-trip for all four rule kinds, DXF Style round-trip for RGB colors, theme colors, and font flags (bold / italic / underline), and XLSX round-trip for Data Bar / 3-color scale / Icon Set with ShowOnly. The fixture is wired intoHotXLSDelphiTests.dprand the matching.dprojso existing CI runs pick it up automatically. - To support the fixture,
TCondFormatexposes two new public accessors:RuleCountandRule(I), so callers can iterate rules after read instead of relying on the previously private rule list.
Version 2.39.0
- The Differential XF (DXF) style override on conditional formatting rules now supports theme color encoding in addition to the existing RGB mode. Each color slot — font color, fill background, fill foreground — can be set against a workbook theme index plus a tint value, so styles like "accent-2 lightened 50%" round-trip without being flattened to a frozen RGB.
- Three new methods on
TXLSDxfStyleopt a slot into theme mode:SetFontColorTheme(themeId, tint),SetFillBgColorTheme(themeId, tint),SetFillFgColorTheme(themeId, tint). Tint is a single in the [-1.0, +1.0] range — negative values darken the theme color, positive values lighten it, 0.0 leaves the base color. The corresponding existingSetXxxColor(rgb)methods stay RGB mode; the two modes are mutually exclusive per slot and whichever was called last wins. - Query the active mode via new
XxxColorIsTheme/XxxColorThemeId/XxxColorThemeTintread-only properties paired with each of the three color slots.HasXxxColorreturns True in either mode; existing callers checking onlyHasXxxColor+ readingXxxColorcontinue to work, treating theme-mode slots as having color 0 (a downgrade rather than a crash). - The BIFF8 wire format follows the standard XFProp Color layout:
xclrType=2+ 4-byte BGR LongWord for RGB (existing v2.35.0 behavior),xclrType=3+ 2-byte signed tint × 32767 + 4-byte theme index for theme mode (new). The DXF blob decoder mirrors this dispatch — incoming Excel-saved files using theme color in their CF12 DXF blocks now expose the theme id and tint through the new properties. - Known limitations: theme-mode round-trip on BIFF8 (.xls) is the only path supported by this release — the XLSX (.xlsx) side still emits and reads
<color rgb="..."/>only and ignorestheme/tintattributes (planned for a follow-up release together with the workbook theme palette plumbing).
Version 2.38.0
- XLSX (.xlsx) reading now recognizes the Excel 2007+ conditional formatting extension rules in incoming files — data bar, color scale (2 and 3 stops, auto-detected from the cfvo count), and icon set — and populates the matching
TXLSXConditionalFormatobject so user code can inspect and round-trip these rules without losing them. Previously, Excel-saved .xlsx workbooks with these rule kinds had them dropped silently on load. - The reader parses the OOXML
<cfRule type="dataBar|colorScale|iconSet">envelope plus the inner<dataBar>/<colorScale>/<iconSet>body, walking child<cfvo>entries to recover each threshold's type (num / min / max / percent / percentile / formula / autoMin / autoMax) and val, and child<color>entries to recover the data bar color or per-stop scale colors. - Icon set rules round-trip the
iconSetfamily name (the 17 baseline Excel 2007 sets — 3-arrows / 3-flags / 3-traffic / 3-signs / 3-symbols / 4-arrows / 4-rating / 4-traffic / 5-arrows / 5-rating / 5-quarters and gray variants), thereverseflag, and theshowValueflag (mapped toShowOnly). - Color values parsed from the OOXML
rgb="AARRGGBB"attribute are converted to the same BGRLongWordconvention (R in the low byte, matching VCLTColorand BIFF8LongRGB) used by the existing write API, so a load-edit-save round-trip preserves the visual colors exactly. - Existing XLSX
<cfRule type="cellIs">handling is unchanged; the dispatch picks the new rule kinds only when thetypeattribute names them, and falls back to the cell-value path otherwise. - Known limitations: theme color references (OOXML
<color theme="N" tint="0.5"/>) are read as 0 instead of being resolved against the workbook theme palette;iconIdper-stop overrides on icon sets are not yet captured;<cfRule>priorityattribute is ignored on read (rules keep their parse order).
Version 2.37.0
- The BIFF8 CFEX record format now carries the full per-stop cfvo array (kind + value string + color, per [MS-XLS] cfvo semantics) instead of just the bar/stop color. Data Bar rules now persist the min/max threshold kinds and values, color scales persist each stop's kind + value + color combination, and icon sets persist each threshold's kind + value alongside the reverse / showOnly flags.
- CFEX writing adds a version flag byte (ver=1) so older HotXLS readers that expect the v2.34.0 minimal CFEX layout silently fall back to the legacy interpretation. The v2.37.0 reader also recognizes ver=0 records produced by older HotXLS builds, so round-trip with mixed-version installations works in both directions for the kind/color fields that the older format carried.
- The reader now detects mixed CONDFMT + CONDFMT12 entries on the same range — a common pattern in Excel-saved files where the old cell-value backup and the new Data Bar / Color Scale / Icon Set rule cover identical sqref. The older CONDFMT entry is flagged via a new
IsShadowedproperty onTCondFormatso user-facing code that iterates the conditional-format collection can skip the duplicate. The writer continues to emit both record families on save for cross-version Excel 2003 ↔ Excel 2007+ compatibility. - New
TotalRangeproperty onTCondFormatexposes the merged-extent range that was previously private; useful for users implementing custom dedup, range queries, or the shadow check pattern. - Known limitations: shadow detection is bounded by an exact bounding-box match (row1/col1/row2/col2 equality). Mixed entries where CONDFMT covers a slightly different sqref subset are not deduped; ColorScale full-cfvo round-trip preserves the 8 standard kind values (num/min/max/percent/percentile/formula/autoMin/autoMax) but does not round-trip Excel's theme-color tint metadata for the stop colors.
Version 2.36.0
- XLSX (.xlsx) output now supports the three Excel 2007+ conditional formatting rule kinds — data bar, color scale (2-stop and 3-stop), and icon set — bringing the XLSX backend to parity with the v2.34.0 BIFF8 (.xls) backend. Four new public methods on
TXLSXWorksheetmirror the XLS API surface:AddCondFormatDataBar,AddCondFormatColorScale2,AddCondFormatColorScale3,AddCondFormatIconSet. - The saved .xlsx file now emits the OOXML
<conditionalFormatting>element with the appropriate child structure for each rule kind:<cfRule type="dataBar"><dataBar><cfvo/><cfvo/><color/>for bars,<cfRule type="colorScale"><colorScale>with 2 or 3 cfvo + color stops, and<cfRule type="iconSet"><iconSet iconSet="...">with the appropriate cfvo set. - Built-in icon-set name mapping covers the same seventeen Excel 2007 baseline sets supported by the XLS backend (3Arrows / 3Flags / 3TrafficLights1 / 3Signs / 3Symbols / 3Symbols2 / 4Arrows / 4ArrowsGray / 4RedToBlack / 4Rating / 4TrafficLights / 5Arrows / 5ArrowsGray / 5Rating / 5Quarters), produced as the standard OOXML
iconSetattribute string. - Threshold types and color values use the same enumeration and color convention as the XLS API — same
TXLSCfValueKindfor num / min / max / percent / percentile / formula, same Delphi BGRLongWordcolor format. Existing XLSX cell-valueAddConditionalFormatcalls continue to behave identically (no regression). - Known limitations: theme color encoding is not yet emitted (OOXML
<color>uses thergbARGB string attribute only); theshowValue="0"attribute is set whenShowOnlyis true on an icon set, buticonIdper-stop overrides are not yet supported. XLSX read-side recognition of incoming data bar / color scale / icon set rules from Excel-saved files remains deferred — only write is wired up in this release.
Version 2.35.1
- The BIFF8 reader now decodes the Differential XF (DXF) bytes attached to a CF12 rule back into the rule's
TXLSDxfStyleproperty bag, in addition to keeping the raw bytes inDxfBlob. Loading an Excel-saved workbook with styled data bar / color scale / icon set rules now exposes the font color, fill colors, bold / italic / underline, and number format id directly through theHasXxx+ value pair on eachRule.Style. - Round-trip behavior: a load-edit-save cycle now reflects any post-load mutation of
Rule.Style. If a user reads a workbook, changesRule.Style.SetFontColor, and saves, the new color wins; if the user doesn't touch the Style, the saved file carries the same overrides as the original. - Unknown
xfPropTypevalues in the DXF blob are skipped rather than rejected, so newer Excel files using property types not yet recognized by this release still load (the raw bytes remain inDxfBlobfor inspection or future round-trip support).
Version 2.35.0
- Conditional formatting rules now carry a Differential XF (DXF) style override so that a CF12 rule firing in Excel can change the cell's font color, fill, font weight, italic, underline, or number format without altering the cell's base XF. The override is exposed as a new
TXLSDxfStyleproperty on eachTCondFormatRule, accessible after creating a rule via the existingAddCondFormatDataBar/AddCondFormatColorScale*/AddCondFormatIconSetAPIs. - Supported style overrides in this release: font color, fill background color, fill foreground color, fill pattern, font bold, font italic, font underline style, and built-in number format id. Each property has a paired
HasXxxflag — only properties explicitly set viaSetXxxemit into the BIFF8 file. - The BIFF8 writer now appends a non-empty DXF block to the CF12 record whenever the rule's Style has at least one override set. The DXF block uses the public XFProp array layout (cxfp + per-property type / size / data tuples) so Excel can pick up the style changes when the rule applies.
- The BIFF8 reader preserves the raw DXF bytes from an Excel-saved file into the rule's
DxfBlobproperty, so a load-edit-save round-trip no longer silently loses the cell styling carried by CF12 rules. Decoding the raw bytes back into theTXLSDxfStyleproperty bag is planned for a follow-up release. - Default behavior is unchanged for rules that don't opt into a style —
cbdxf = 0is emitted, identical to v2.34.0, so existing data bar / color scale / icon set rules render exactly as before. - Known limitations: theme-color encoding for DXF is not supported (RGB only); border overrides and gradient stops are not in this release's property set; the wire-format byte ordering for the XFProp value blobs follows public [MS-XLS] notes and may require small tweaks for full Excel round-trip fidelity once validated against BiffView. XLSX-side parity for the three new rule kinds remains deferred.
Version 2.34.0
- New public conditional-formatting API on the worksheet for the Excel 2007+ extension rules:
AddCondFormatDataBar,AddCondFormatColorScale2,AddCondFormatColorScale3, andAddCondFormatIconSet. Each method accepts a sqref range like"A1:A10"plus the kind-specific parameters (bar color, gradient stop colors, or icon-set family) and returns the created rule object. - The BIFF8 (.xls) writer now emits the corresponding CONDFMT12 / CF12 / CFEX record family alongside the existing cell-value CONDFMT / CF records, so workbooks created via the new API persist their data bar, 2-color or 3-color scale, and icon-set rules to disk.
- The BIFF8 reader recognizes incoming CONDFMT12 / CF12 / CFEX records and exposes them through the same in-memory rule model, so Excel-authored .xls files with extension rules are no longer silently dropped on load.
- Built-in icon-set catalog covers the seventeen Excel 2007 baseline sets — 3-arrow / 3-flag / 3-traffic-light / 3-sign / 3-symbol families, 4-arrow / 4-rating / 4-traffic-light, and 5-arrow / 5-rating / 5-quarter — selectable through the
TXLSIconSetTypeenumeration. - Threshold types for data bar bounds and color-scale / icon-set stops can be any of number, min/max of range, percent, percentile, or formula, matching the cfvo kinds used by Excel.
- Existing cell-value conditional formatting behavior is unchanged; only new APIs and new BIFF8 record handling were added.
- Known limitations in this release: XLSX output for the three new rule kinds is not yet wired up (existing XLSX cell-value rules unaffected); the differential-XF style block on CF12 is emitted empty so Excel may render bars and scales with default colors instead of the API-supplied colors; per-stop cfvo arrays carry only the color in CFEX, not the kind/value combination. These gaps are planned for the v2.34.x / v2.35.x follow-up releases together with the demo workbook and Excel-rendering validation.
Version 2.33.3
- New XlsTables Delphi demo under
Demo/Delphi/XlsTables/demonstrates the BIFF8 Tables (ListObjects) write side end-to-end. It generatesSalesTable.xlswith a four-column sales region (Region / Product / Quarter / Revenue) bound as a Table at A1:D10 usingSheet.AddTable('SalesTable', 'A1:D10', headers). Open the produced .xls in Excel to verify the column dropdowns, banded-row stripes, and the SalesTable structured-reference name in the Name Box. - The demo is the shortest end-to-end recipe for adopting the v2.33.0
AddTableAPI: it setsStyleName = 'TableStyleMedium2'andShowRowStripes = True, fills nine data rows + one header row, and writes the workbook with a singleSaveAscall. Output goes next to the executable; an "Open Output Folder" button is provided for convenience. - Compilation infrastructure is unchanged: the existing
build-Win32-Demo.cmdpicks upXlsTables.dprautomatically via itsfor /r Demo *.dprsweep — no changes to build scripts or .cbproj/.dproj configs required.
Version 2.33.2
- BIFF8 (.xls)
SaveAsnow suppresses the standalone sheet-levelAUTOFILTERINFO/AUTOFILTERrecords ($009D / $009E) when a Table on the same sheet fully covers the autofilter range. Excel embeds an autofilter dropdown inside the Table object itself, so emitting an additional sheet-level autofilter for the identical range triggers Office File Validation to reject the file. This matches the XLSX-side fix shipped in v2.29.4. - The coverage check is conservative: a Table is considered to "cover" the autofilter only when its first/last row and first/last column bracket the autofilter's bounds. Partial overlap (e.g. autofilter spanning extra rows below a Table) keeps emitting the sheet-level autofilter so legacy workflows that put a filter above or beside a Table region continue to work.
- BIFF5 saves and sheets without any Table are unaffected — the suppression check only runs when the file format is BIFF8 and the worksheet has at least one Table. No public API change.
Version 2.33.1
- BIFF8 (.xls) read side now recognizes the shared-feature Tables records that v2.33.0 added on the write side. Opening an .xls saved by Excel (or by HotXLS itself) with one or more ListObjects/Tables now populates the worksheet's
Tablescollection with aTXLSTableper parsed FEAT11 ($0872), reconstructing the table's range from the primary Ref8U, recovering the table id, name, and display name when present, and filling in the table style name from the paired LIST12 ($0877) record. Previously these records were silently ignored, dropping all Table state on round-trip. - The shared FEATHEADR ($0867) opcode is now dispatched by its
Isffield instead of always going to the protection parser. Isf=2 (Enhanced Protection) keeps the existing behavior; Isf=4 (SharedList) is acknowledged as the marker for Tables on the sheet; Isf=3 (SmartTags) and other values are safely ignored. FEATHEADR11 ($0871) is also acknowledged silently as a Tables marker. - The reader is best-effort and tolerant: malformed or Excel-variant FEAT11 payloads degrade gracefully to a Table that has at least the correct cell range, even if the higher-offset fields (per-column ids, structured-reference data) cannot be parsed. Sheet protection round-trip is unchanged — the existing options bit at Isf=2 payload offset 19 is still read identically.
Version 2.33.0
- BIFF8 (.xls) worksheets now support Tables (Excel ListObjects). Call
Worksheet.AddTable(Name, Range, Columns)on anIXLSWorksheetto attach a named table region to a cell range — for exampleSheet.AddTable('SalesTable', 'A1:F9', HeaderList). The .xls save path emits the BIFF8 shared-feature record family that marks the sheet as containing Tables: FEATHEADR ($0867 with Isf=4), FEATHEADR11 ($0871), and a paired FEAT11 ($0872) + LIST12 ($0877) per table. Previously, callingAddTableon the XLS facade was unavailable; tables defined on a worksheet were silently dropped onSaveAs(xlExcel97). - New public API on
IXLSWorksheet:Tables: TXLSTablesproperty (read-only collection) andAddTablemethod. The shape mirrorsTXLSXTable/TXLSXTableson the XLSX side, so code that adds tables to a workbook can be format-agnostic.TXLSTableexposesId,Name,DisplayName,Range,Columns,StyleName(default'TableStyleMedium2'),ShowFirstColumn,ShowLastColumn,ShowRowStripes(default true),ShowColumnStripes, andTotalsRowShown. - The new sheet-stream records are emitted just before the worksheet EOF, after sheet protection, only when the file format is BIFF8 (
xlExcel97). Workbooks without tables save exactly as before — no FEATHEADR is emitted, no record stream changes, no overhead. BIFF5 saves are unaffected since Tables are a BIFF8+ concept. - Table ids are assigned sequentially per sheet (1..N) at save time if not pre-set, so callers can leave
Idat its default afterAddTable. The default style name'TableStyleMedium2'matches what Excel and the XLSX side already use, keeping table appearance consistent across both file formats. - Known limitations of this initial release (v2.33.0): totals-row formulas and structured-reference auxiliary ranges are not yet emitted; conflict suppression between sheet-level
AUTOFILTERINFOand a Table range will be added in a follow-up; the read side (parsing an Excel-saved .xls with Tables back into theTablescollection) is also a follow-up. Tables created via this v1 write path are well-formed for round-trip preservation in Excel itself.
Version 2.32.1
- Fixed a compilation error introduced by the v2.32.0 FILEPASS write-side feature that prevented the library from building on RAD Studio 12+ with strict type-checking. The
EncryptAllBlobshelper declared its scatter buffer asPBytein a context wherePByteresolved tolxHandle's local alias, while the call targetEncryptBlobDatainlxEncrypterexpectedlxBLOB.PByte. Although the two types both alias^Byte, Delphi treats them as distinct under E2010 strict type-checking. The buffer variable is now fully qualified aslxBLOB.PByte, so the v2.32.0 password-encryption path compiles cleanly across all supported RAD Studio versions (12.0–37.0).
Version 2.32.0
- Added
EncryptionPasswordproperty onIXLSWorkbook. Setting a non-empty password beforeSaveAsnow produces an Excel-compatible password-protected .xls file: Excel, LibreOffice Calc, and other BIFF8 readers prompt for the password on open and refuse access when it is wrong. Previously the password setter was silently ignored on the write side and workbooks were always emitted in plaintext. - The write path emits the BIFF8 FILEPASS record ($002F) immediately after the workbook BOF and RC4-encrypts every subsequent record body using the existing read-side algorithm (vMajor=1, vMinor=1 -- Excel "Office 97/2000 Compatible" encryption). 16-byte Salt and Verifier are generated via Windows
CryptGenRandomrather than PascalRandom. Block-key re-derivation at every 1024-byte boundary is preserved so large workbooks, SST CONTINUE chains, and multi-sheet streams encrypt correctly. - Encryption applies to BIFF8 (
xlExcel97) only -- the BIFF5 save path remains plaintext. EmptyEncryptionPasswordkeeps the legacy plaintext output and is the default, so existing applications that never call the setter are unaffected. - Read side: fixed a latent bug in
ParseFilePassthat caused the first record body after FILEPASS (typically CODEPAGE) to be treated as plaintext. HotXLS-written encrypted files now round-trip through HotXLS itself, and Excel-written encrypted files no longer hand back a garbage CODEPAGE value. - Security note: BIFF8 RC4 with vMajor=1/vMinor=1 uses a 40-bit key, considered broken by 2025 standards. Use this feature for compatibility with Excel's classic password-protection UI, not for confidentiality of sensitive data. For real security, prefer XLSX with AES.
Version 2.31.0
- Added
UseSharedFormulasproperty onIXLSWorkbook. When set toTrue, BIFF8 SaveAs groups cells that share a template formula into native SHRFMLA records ($04BC), matching Microsoft Excel's compact wire format. Columns of formulas like=A1*B1,=A2*B2, …,=A99*B99are grouped automatically, reducing file size by 30–60 % on template-heavy sheets. - Grouping is column-down, contiguous, and template-based: only cells in the same column with consecutive rows and PTG-equivalent formulas are grouped. Cells with array formulas or merged-cell flags are excluded.
- Default is
False, keeping the existing per-cell FORMULA record emission unchanged. Opt in by settingWorkbook.UseSharedFormulas := Truebefore callingSaveAs. - Reading side is unaffected: HotXLS has always read SHRFMLA records correctly, so round-tripped files continue to work. This is a MINOR version bump (2.30.x → 2.31.0) because a new public property is added and BIFF8 output format changes when the feature is enabled.
Version 2.30.6
- Fixed a fourth class of compilation error in the C++Builder demos under RAD Studio 37.0's clang-based bcc32 compiler: indexing a border style through a smart-pointer interface using
->Borders[idx]->Proptriggered error E5843 ("member reference type is not a pointer"). All occurrences have been changed to->Borders->Item[idx]->Prop, which accesses the indexed property directly on the interface. This fix appears in the ApiTour, ExportWithColoring, PurchaseOrder, and QuickStart demos and completes full clang compatibility for all nine C++Builder demo projects.
Version 2.30.5
- All nine C++Builder demo projects now build cleanly under RAD Studio 37.0's clang-based bcc32 compiler. Fixed three classes of compilation error: the
lxFormulapropertyEOFwas renamedIsEofto eliminate a conflict with the Cstdio.hmacro of the same name; XLS API enum constants (xlHAlignCenter,xlAround,xlMedium, etc.) are now explicitly qualified with theLxhandle::namespace in C++Builder demo code; and XLSX cell-value assignments changed fromOleVariant(...)toVariant(...)becauseTXLSXCell.ValueisSystem::Variantand the clang compiler rejects the implicit protected-base-class conversion fromOleVariant.
Version 2.30.4
- The OrderCalc demo (Delphi Purchase Order sample) now includes an XLSX export option alongside the existing XLS, HTML, and RTF formats. Selecting "Excel 2007+ file (*.xlsx)" in the save dialog writes the purchase order using the native TXLSXWorkbook API, producing a standards-compliant XLSX file with fonts, fills, borders, alignment, number formats, and Excel formulas preserved.
Version 2.30.3
- SaveXLSWorkbookAsXLSX (the bridge used by TDataToXLS.SaveAs and TGridToXLS.SaveAs when the target filename ends in .xlsx) now copies cell background fill colors, font colors, and font style attributes (name, size, bold, italic) from the underlying XLS workbook to the XLSX output. Column widths — including explicitly narrowed columns set before export — are also copied. Previously only cell values and number formats were transferred, so the XLSX output was uniformly unstyled, all column widths were at the default, and any deliberately-narrow decorative column appeared as an oversized blank column.
Version 2.30.2
- Fixed a round-trip bug in the XLS reader where the Outline.SummaryRow direction was inverted after Open. The WSBOOL record's fRwSumsBelow flag (BIFF8 spec p278 byte 0 bit 6, mask $0040) means "summary rows appear *below* detail", but the parser previously set SummaryRow = xlAbove when the bit was set (and xlBelow when clear) -- exactly the opposite of the spec. The writer side was already correct (emits $0040 for xlBelow), so opening an Excel file with "summary rows below detail" and re-saving it via HotXLS would silently flip the outline direction to "above". Now the parse direction matches the spec and the writer, so outline configurations round-trip correctly. fColSumsRight ($0080) was already handled correctly and is unchanged.
Version 2.30.1
- XLS (BIFF8) writer now emits INTERFACEHDR / INTERFACEEND / WRITEACCESS records in the workbook globals section, matching Microsoft Excel's native record layout. Native Excel always writes this trio between CODEPAGE/DSF and WINDOW1, and stricter BIFF readers / Office File Validation paths may expect them. INTERFACEHDR carries the code page (1200, UTF-16), INTERFACEEND is an empty marker, and WRITEACCESS carries the "saved by" user name (currently hard-coded to "HotXLS"; a future release may promote this to a Workbook property). Output XLS files now match Excel's native byte layout more closely.
Version 2.30.0
- Added a classic-to-XLSX export bridge. TDataToXLS.SaveAs and TGridToXLS.SaveAs now write .xlsx file names through the XLSX engine while keeping the existing .xls, .html, and .rtf paths unchanged.
- Added worksheet view APIs for XLS and XLSX. IXLSWorksheet.View and TXLSXWorksheet.View support normal view, page-break preview, and page-layout view, and the setting is preserved by the relevant file writer/reader path.
- Added classic workbook compatibility APIs: IXLSWorkbook.SetCodePage and IXLSWorkbook.VBAProject. VBA projects can now be inspected through TXLSVBAProject/TXLSVBAModule module names and source text when a workbook contains a VBA storage.
- Extended TXLSXWorkbook with ParsedVBAProject, a read-only parsed view over a valid vbaProject.bin payload, while preserving the existing raw VbaProject byte round-trip behavior.
- Added the optional cxGridAddExcel unit for DevExpress cxGrid export workflows. It is intentionally not included in the default packages so installations without DevExpress continue to build cleanly.
- Fixed XLSX chart sheet rendering where the chart appeared blank when the workbook was opened in Excel (a freshly opened chart sheet briefly showed the 3 chart-context icons in the top-right and then dropped them, leaving the sheet empty). Chart sheet drawings now use <xdr:absoluteAnchor> with explicit extent (~10in × 7.2in = 9525000 × 6858000 EMU) instead of <xdr:twoCellAnchor> with cx=0/cy=0; chart sheets have no cell grid to resolve twoCellAnchor against, so Excel was falling back to the inner xfrm of zero size and rendering the chart at zero pixels. Per ECMA-376-1 14.2.3.2, chart sheet drawings must use absoluteAnchor. Worksheet chart anchors are unchanged.
Version 2.29.4
- Fixed an XLSX packaging bug that caused Excel's repair prompt "We found a problem with some content in <file>.xlsx" on workbooks where a sheet-level autoFilter and a Table cover the same range (e.g. the MemoryDataExport demo's XlsxFeatureGallery.xlsx Details sheet, which called AddTable('SalesTable', 'A1:F9', ...) followed by SetAutoFilter('A1:F9')). OOXML forbids two filter mechanisms over the same range; the writer now suppresses the duplicate sheet-level <autoFilter> emission when its range coincides with any table's range. The Tables' own autoFilter (rendered as the table header dropdowns) still works.
- Fixed a related schema violation in <dataValidation> output: an operator attribute was emitted for every validation, but ECMA-376-1 18.3.1.32 only allows it for types whole / decimal / date / time / textLength. AddListValidation produces type="list", which used to leak operator="between"; OFV is stricter than Excel about this and could reject the workbook. The operator attribute is now omitted for list / custom / none.
- Fixed the <legacyDrawing r:id="..."/> element on worksheets that mix internal and external hyperlinks with comments (XlsxFeatureGallery.xlsx Dashboard sheet has 1 internal + 1 external + 1 comment). The rId calculation counted all hyperlinks instead of only the externals (internals use the inline location= attribute and do not consume an rId), so legacyDrawing pointed at the comments rels target instead of the vmlDrawing target. ECMA-376-1 18.3.1.51 requires legacyDrawing to reference a vmlDrawing-typed rels. The rId is now computed from the external hyperlink count only.
- Also stopped emitting a redundant xl/worksheets/_rels/sheetN.xml.rels file for chart sheets added via AddChartSheet. The duplicate relationship file pointed at a nonexistent worksheet part and showed up as an orphan inside the .xlsx zip; chart sheets now write only the correct xl/chartsheets/_rels/sheetN.xml.rels.
- Updated the MemoryDataExport demo (XlsxFeatureGallery) to drop the redundant SetAutoFilter('A1:F9') call on the Details sheet -- AddTable already attaches an autoFilter to the table range, so the extra call previously triggered the OFV rejection above. The writer's defensive suppression covers customer code that does not get the demo update.
Version 2.29.3
- Fixed an XLSX round-trip regression where borders drawn on otherwise-empty rows were dropped after Open + modify + SaveAs. ApiTour's row 8 horizontal divider (produced by a Range.SetBorders call on a row with no data) rendered correctly in the freshly generated ApiTour.xlsx but disappeared in ApiTour-XLSX-Modified.xlsx after the demo's Modify pass re-saved the file, leaving Excel showing a broken vertical grid line at C8 / D8 in the modified file only (the original ApiTour.xlsx was unaffected). The XLSX parser now also applies cell styles when a `<c>` element is self-closing or has no value/formula children, so styled valueless cells survive the round trip.
Version 2.29.2
- Fixed an XLSX round-trip regression in Open + modify + SaveAs flows. When ParseWorksheetXml read a cell whose
referenced a styles.xml cellXf, it stored N directly on cell.FormatIndex. On SaveAs the cellXfs are rebuilt from the workbook style pools (with optional composite entries) and the new layout's slot at the same numeric index almost never carries the same semantics; the resaved cell ended up pointing at the wrong cellXf -- or past the end of the new cellXfs table -- and Excel reported "Office has detected a problem with this file. We can try to recover". ApplyStyle now reverse-maps cellStyle to FontIndex / FillIndex / BorderIndex / NumberFormatIndex / AlignmentIndex / ProtectionIndex via the CellXfXxxMap tables that ParseStylesXml already populates, so the round trip re-emits the cell against the freshly-built cellXfs.
Version 2.29.1
- Fixed two XLSX rendering regressions exposed by ApiTour demo: (1) cells inside a row could be emitted out of column order when a Range.SetBorders call added cells in non-monotonic order (e.g. outline pass creates A8/C8, inside pass creates B8 -- output was A8 / C8 / B8 / instead of A8 / B8 / C8 / so Excel reported "Office has detected a problem with this file" and forced Protected View). The XLSX writer now sorts each row's cells by column before emission, matching the OOXML CT_Row schema. (2) Cells that set 2 or more style indices simultaneously (Font + Fill + Border, etc.) used to be collapsed to a single-dim cellXf via priority chain (FontIndex won, Border / Fill were dropped), so e.g. a header row with FontIndex + FillIndex + BorderIndex set on a Range[..].SetBorders rendered without the borders. The writer now collects a composite cellXf pool during SaveAs and routes multi-dim cells to a synthesized cellXf that preserves every dimension.
Version 2.29.0
- Renamed the internal helper unit `lxList2` to `lxKeyList` and renamed `lxList`'s WideString-key classes from `TXLSKeyList` / `TXLSKeyEntry` / `TXLSKeyArray` to `TXLSStringKeyList` / `TXLSStringKeyEntry` / `TXLSStringKeyArray` so the unit name matches its main class `TXLSObjectKeyList` and the two key-list generations are self-describing (`TXLSStringKey*` for the legacy WideString-key pool, `TXLSObjectKey*` for the THashtableKey-based dedup pool). If your code references the old unit name or class names, update your uses clauses and type names accordingly.
Version 2.28.3
- Fixed an XLSX cell-emit regression that caused borders set on otherwise-empty rows to disappear. `Range[''A3:C10''].SetBorders(xlsxEdgeOutline,...)` on a sheet whose row 8 carried no data used to render the C/D vertical grid line broken across that row because the writer skipped any cell whose Value was unassigned -- even when the cell had a BorderIndex / FontIndex / FillIndex / NumberFormatIndex / AlignmentIndex / ProtectionIndex assigned. The writer now emits `
` for valueless cells that carry any style index.
Version 2.28.2
- Fixed an XLSX worksheet schema violation that caused Excel to refuse loading sheets which combined merged cells with AutoFilter. The XLSX writer now emits the <autoFilter> element before <mergeCells>, matching the OOXML CT_Worksheet sequence (autoFilter is #11, mergeCells is #15). Previously such workbooks triggered Excel's repair dialog with `Load error. Line 1, column 0` on sheet1.xml and recovery would also drop tableParts / autoFilter from sibling Details sheets.
Version 2.28.1
- Added convenience APIs to the classic XLS facade. IXLSRange now exposes read-only Width and WidthInPixels helpers plus SaveAsCSV(FileName); IXLSWorksheet and IXLSWorkbook now expose SaveAsCSV(FileName / Stream) overloads for the used range or active sheet; IXLSPageSetup now exposes Order, Draft, BlackAndWhite, PrintNotes, and read-only IsFitToPages. SETUP record save/open now preserves those print flags.
Version 2.28.0
- Added RTF export on TXLSXWorkbook. New SaveAsRTF overloads (FileName / Stream x default-active-sheet / explicit SheetIndex) write the chosen sheet as an RTF 1.6 document containing a plain table. Column widths are derived from the sheet's ColWidth data (1 Excel character unit is approximated as 96 RTF twips); columns without explicit widths fall back to StandardWidth if set, otherwise to 809 twips (~8.43 chars). Per-cell bold (\\b), italic (\\i), and font size (\\fsN in half-points) are applied from the cell's FontIndex. Non-ASCII characters are encoded as signed-16-bit RTF \\uN? unicode escapes per RTF 1.6 §2.4.2, covering the full BMP range. Merged cells are not spanned — origin cells carry the content and non-origin cells within the range are emitted as empty cells. Default SheetIndex writes the active sheet; out-of-range returns -1. Empty sheets produce a minimal valid RTF document.
Version 2.27.0
- Added HTML export on TXLSXWorkbook. New SaveAsHTML overloads (FileName / Stream x default-active-sheet / explicit SheetIndex) write the chosen sheet as a UTF-8 HTML document with a UTF-8 BOM. Output is a single <table> inside a minimal HTML page. Merged cells are expressed as colspan / rowspan attributes. Cell styling is emitted as inline CSS: font family, size (pt), bold (font-weight:bold), italic (font-style:italic), strikethrough (text-decoration:line-through), and explicit font color; solid-pattern fill foreground color as background-color; horizontal alignment (left / center / right / justify) and WrapText (white-space:pre-wrap). Date variant values render as 'yyyy-mm-dd hh:nn:ss'; rich-text cells flatten to their concatenated run text. Theme colors are silently skipped (no theme1.xml resolution in the simple HTML export). Default SheetIndex writes the active sheet; out-of-range returns -1.
Version 2.26.0
- Added theme color + indexed color + tint/shade support on TXLSXFont, TXLSXFill, and TXLSXBorderEdge. Each color slot now carries three new fields beside the existing RGB Color: ColorTheme (Integer; -1 = unset; 0..11 = OOXML theme slot), ColorIndex (Integer; -1 = unset; 0..63 = OOXML legacy palette slot — pairs with Workbook.IndexedColor[N] and XlsxDefaultIndexedPalette), and TintAndShade (Double in [-1.0, 1.0]; only emitted with theme). TXLSXFill exposes Foreground / Background variants of all three. Writer priority: theme > indexed > rgb; the older "ColorIsAuto = True" path keeps suppressing the element entirely. SaveAs and Open round-trip the full <color theme="N" tint="..."/> and <color indexed="N"/> attribute sets on font / fill / border colors. Bookkeeping: TXLSXBorders.LookupOrAdd now compares all three new fields so theme-colored borders don't collide with rgb-colored entries in the pool.
Version 2.25.0
- Added external workbook link round-trip on TXLSXWorkbook. New types: TXLSXExternalLink (Target URL + SheetNames list) + TXLSXExternalLinks collection. TXLSXWorkbook.ExternalLinks property exposes the collection; Add(Target) appends and returns a new link the caller fills out. SaveAs emits a workbook-level <externalReferences> block in xl/workbook.xml, an externalLink rel in xl/_rels/workbook.xml.rels (tail of the rid range so existing rids stay stable), the xl/externalLinks/externalLink{N}.xml part per entry (<externalBook><sheetNames>) and its sibling .rels file pointing at the remote workbook URL with TargetMode="External", plus matching ContentType Overrides. Open scans externalLink{N}.xml + .rels pairs starting at index 1 and stops at the first gap. The current release round-trips Target URL + SheetNames only; cached cell values inside <sheetDataSet> are not preserved.
Version 2.24.0
- Added composing border helper TXLSXRange.SetBorders. New TXLSXBorderEdgeKind enum (xlsxEdgeAll / Outline / Inside / InsideHorizontal / InsideVertical / Top / Bottom / Left / Right / DiagonalUp / DiagonalDown) selects which edges to write. SetBorders(Kind, Style, Color) / SetBorders(Kind, Style) (defaults to opaque black) iterates every cell in the range, computes the edge mask per cell position (corner cells get two outline edges, edge cells get one, interior cells get inside grid lines, etc.), composes Style + Color onto a clone of the cell's current border (so successive Outline + Inside calls stack instead of overwriting), then does a workbook-level lookup-or-add on Workbook.Borders and writes the resulting 1-based BorderIndex back on the cell. New TXLSXBorders.LookupOrAdd helper returns 0 for empty borders so the pool never bloats with no-op entries.
Version 2.23.0
- Added CSV export overloads on TXLSXWorkbook. New entry points: SaveAsCSV(FileName) / SaveAsCSV(FileName, SheetIndex, Delimiter) / SaveAsCSV(Stream) / SaveAsCSV(Stream, SheetIndex, Delimiter) write the chosen sheet's populated extent as UTF-8 with leading BOM and CRLF line terminators. Default variants pick the active sheet and comma delimiter; the SheetIndex/Delimiter overload accepts any WideChar (commonly tab #9 for TSV). Field quoting follows RFC 4180 — values containing the delimiter, double quote, CR or LF are wrapped in quotes with embedded quotes doubled. Date values render as 'yyyy-mm-dd hh:nn:ss'; rich-text cells flatten to the concatenated run text; empty cells in the interior of the bounding rectangle render as bare delimiters and the row stops at the last populated column.
Version 2.22.0
- Added workbook-level indexed color palette overrides on TXLSXWorkbook. New entry points: IndexedColor[Index: Integer]: LongWord (read/write, valid slots 0..63, ARGB LongWord), HasCustomIndexedColor(Index): Boolean (reports whether the caller has overridden a slot), CustomIndexedColorCount: Integer (number of overridden slots; 0 = palette emits as default), ResetIndexedColors: procedure (discard every override). The new XlsxDefaultIndexedPalette constant exposes the OOXML legacy ECMA-376 palette for slots 0..63. SaveAs emits <colors><indexedColors> with all 64 <rgbColor> children only when at least one slot has been overridden; untouched slots fall back to the default palette so files re-saved without changes stay clean. Open parses the block and silently drops slots whose value matches the default so round-trips stay minimal. Out-of-range slot assignments (< 0 or > 63) are dropped instead of raising.
Version 2.21.0
- Added six sheet-view display properties on TXLSXWorksheet. Zoom: Integer is the zoom percent shown in Excel's status bar (10..400, default 100; out-of-range assignments clamp on write). DisplayGridLines / DisplayZeros: Boolean (default True) hide cell grid lines or render zero-valued cells as empty when set False. DisplayRightToLeft: Boolean (default False) flips the sheet to RTL layout for Arabic / Hebrew / etc. StandardWidth / StandardHeight: Double set the default column width (Excel character units) / default row height (points); 0 = unset, in which case Excel applies its built-in 8.43 / 15 defaults. SaveAs emits <sheetView showGridLines / showZeros / rightToLeft / zoomScale> and <sheetFormatPr defaultColWidth / defaultRowHeight> only when the values differ from the Excel defaults; Open parses them back. Existing freeze-pane + tab-selected handling on <sheetView> is unchanged and now shares a single emitter.
Version 2.20.7
- Added TStream overloads on TXLSXWorkbook for in-memory serialization. New entry points: SaveAs(Stream: TStream) / SaveAs(Stream: TStream; FileFormat: TXLSXFileFormat) write the entire .xlsx package into the given stream starting at the current position; Open(Stream: TStream) reads it back. The caller owns the stream and must Free it. Useful for HTTP responses, blob storage, in-memory pipelines, and unit tests that bypass the local file system. The existing file-based SaveAs / Open overloads are unchanged and now delegate to the same internal write / read helpers.
Version 2.20.6
- Added Hidden / Visible / Comment properties to TXLSXDefinedName. Hidden maps to the OOXML <definedName hidden="1"/> attribute (defaults False / Visible=True; True hides the name from Excel's Name Manager UI). Visible is a Boolean alias (inverse of Hidden) for clearer call-site code. Comment maps to <definedName comment="..."/>. SaveAs emits both attributes when they differ from defaults; Open parses them back. SheetIndex (the existing Local scope binding via localSheetId) is unchanged.
Version 2.20.5
- Added five more font traits to TXLSXFont. New TXLSXFontVertAlign enum (xlsxVertAlignBaseline / Superscript / Subscript) drives the existing Excel "Superscript" / "Subscript" font checkboxes via a single VertAlign property (mutually exclusive in the UI). New Boolean OutlineFont and Shadow properties for the matching Excel font effects. New Integer Family (OOXML font-family number: 1=Roman / 2=Swiss / 3=Modern / 4=Script / 5=Decorative; 0=unset) and CharSet (GDI charset number: 0=ANSI / 1=Default / 134=GB2312 / 136=ChineseBig5 / etc.; -1=unset). SaveAs emits the matching <vertAlign> / <outline/> / <shadow/> / <family> / <charset> children inside <font>; Open parses them back. ColorIndex / ThemeColor / TintAndShade are deferred to the upcoming theme milestone.
Version 2.20.4
- Added border diagonal direction toggles. TXLSXBorder now exposes DiagonalUp: Boolean and DiagonalDown: Boolean. The two flags share the existing Diagonal edge's Style and Color and pick which diagonal direction(s) Excel renders — up-only (bottom-left to top-right), down-only (top-left to bottom-right), or both. SaveAs emits diagonalUp / diagonalDown attributes on the <border> element when set; Open parses them back. Previously the engine wrote one Diagonal edge with no direction control, so Excel never drew either diagonal line.
Version 2.20.3
- Added workbook Date1904 toggle. TXLSXWorkbook now exposes a Date1904: Boolean property that selects between the Windows 1900 epoch (default; matches Excel for Windows) and the Mac 1904 epoch (Excel for Mac legacy). SaveAs emits <workbookPr date1904="1"/> in xl/workbook.xml when set; Open reads it back. The flag is metadata only — the engine does not transform stored serials; callers must set Date1904 before assigning TDateTime cell values so the round-trip lands on the right calendar.
Version 2.20.2
- Added auto-filter per-column criteria support. New types: TXLSXAutoFilterOp enum (Equal / NotEqual / GreaterThan / LessThan / GreaterOrEqual / LessOrEqual), TXLSXAutoFilterColumn class (ColId + 1 or 2 criteria + AND/OR connector), TXLSXAutoFilterColumns collection. TXLSXWorksheet exposes AutoFilterColumns property and AddAutoFilterColumn(ColId, Op, Criteria) / AddAutoFilterColumn(ColId, Op1, Criteria1, Op2, Criteria2, AndConnector) / ClearAutoFilterColumns convenience methods. SaveAs expands the existing <autoFilter ref="..."/> into a body of <filterColumn colId=N><customFilters and=0|1><customFilter operator="..." val="..."/>...</customFilters></filterColumn> per column when criteria are set; Open parses the same structure back. Previously the engine only stored the autofilter range (sqref) and Excel showed empty filter dropdowns.
Version 2.20.1
- Added manual page break support on TXLSXWorksheet. New entries: AddRowBreak(BeforeRow) / AddColBreak(BeforeCol) to insert a horizontal or vertical page break before a given row / column (Excel UI "Before" semantics — row N starts a new page). HasRowBreak / HasColBreak check; RemoveRowBreak / RemoveColBreak drop a single entry; ClearRowBreaks / ClearColBreaks / ClearAllPageBreaks bulk-clear. RowBreakCount / ColBreakCount + indexed RowBreaks(Index) / ColBreaks(Index) expose the stored 1-based "Before" indices for iteration. SaveAs emits <rowBreaks> / <colBreaks> with <brk id="N-1" max="..." man="1"/> children between <headerFooter> and <drawing>; Open parses them back.
Version 2.20.0
- Added sheet visibility (Hidden / VeryHidden), per-sheet selection and active-sheet tracking on TXLSXWorkbook / TXLSXSheets / TXLSXWorksheet. New types: TXLSXSheetVisibility enum (xlsxSheetVisible / xlsxSheetHidden / xlsxSheetVeryHidden). New TXLSXWorksheet properties: Visibility, Visible (Boolean alias), IsSelected (read-only). New TXLSXSheets members: ActiveIndex, Activate(Index), Move(FromIndex, ToIndex). New TXLSXWorkbook.ActiveSheet proxy. SaveAs emits <sheet state="..."/> on the workbook's sheet entries, <bookViews><workbookView activeTab=N/></bookViews> when the active sheet is not the default 0, and tabSelected="1" on the active sheet's <sheetView>; Open parses all three back into the new properties. The first added sheet becomes both visible and active by default so existing call sites continue to work.
Version 2.19.2
- Added four more PageSetup attribute toggles on TXLSXWorksheet to round out the OOXML <pageSetup> coverage: BlackAndWhite (B/W rendering), Draft (suppress graphics for faster preview), PrintNotes (print cell comments as displayed instead of hiding them), and PrintOverThenDown (left-to-right rows first instead of the default top-to-bottom columns first traversal). SaveAs emits the matching blackAndWhite / draft / cellComments / pageOrder attributes; Open parses them back. Defaults stay off so unchanged worksheets do not gain extra attributes.
Version 2.19.1
- Added three-part header and footer convenience properties to TXLSXWorksheet. New entries: LeftHeader / CenterHeader / RightHeader and LeftFooter / CenterFooter / RightFooter. Each reads or writes the matching &L / &C / &R segment of the existing HeaderText / FooterText raw string; writing any one segment rebuilds the full raw form so the round-trip writer and reader keep working without changes.
- Added print-option toggles for sheet-level page output. New TXLSXWorksheet properties: CenterHorizontally, CenterVertically, PrintGridlines, PrintHeadings. SaveAs emits a <printOptions horizontalCentered / verticalCentered / gridLines / headings/> element when any of the four is enabled; Open parses the same attributes back. Defaults stay off so unchanged worksheets do not gain an extra <printOptions/> element.
- Added per-sheet PrintArea, PrintTitleRows and PrintTitleCols round-trip. These map to the OOXML built-in workbook-level _xlnm.Print_Area and _xlnm.Print_Titles defined names scoped via localSheetId. PrintArea takes a bare A1 reference (e.g. "$A$1:$D$10"); PrintTitleRows and PrintTitleCols take row-only or column-only references (e.g. "$1:$3" or "$A:$B") and combine into a single Print_Titles entry. The writer prepends the sheet name with proper quoting (single quotes around names that contain spaces or special chars); the reader strips the prefix and routes the entries back into the matching sheet so they do not leak into the user-facing TXLSXWorkbook.DefinedNames collection.
Version 2.19.0
- Added row / column insert and delete operations with full sheet-wide shift on TXLSXWorksheet. New entries: InsertRows(BeforeRow, Count), DeleteRows(StartRow, Count), InsertCols(BeforeCol, Count), DeleteCols(StartCol, Count). InsertRows / InsertCols push existing rows / columns away from the cut by Count; DeleteRows / DeleteCols drop the [Start, Start+Count-1] band and pull everything past the cut back by Count.
- The shift applies across every piece of sheet geometry that depends on row / column position: cell anchors (Cells), merged-range corners (MergedCells — straddling ranges are clipped to the surviving edges and inside-only ranges are removed), the row / column metadata lists (FRowHeights / FColWidths / outline levels / hidden / collapsed), Hyperlink and Comment anchors (entries inside a deleted band are removed), FreezePane (FFreezeRow / FFreezeCol), AutoFilterRange, and the per-entry Range string on every ConditionalFormat, DataValidation, and Excel Table (an entry whose range falls entirely inside the cut is removed).
- New Delete(Index) members on the supporting collections so the shift logic can remove a single entry without a Clear: TXLSXHyperlinks, TXLSXComments, TXLSXConditionalFormats, TXLSXDataValidations, TXLSXTables.
- Out of scope in this release: Images and Charts are not shifted (their EMU pixel anchors decouple them from the cell grid and a faithful shift would require recomputing the EMU offsets); workbook-level DefinedNames formulas are not rewritten. Both are tracked for a follow-up release.
Version 2.18.2
- Added AutoFit helpers for column widths and row heights. TXLSXWorksheet exposes AutoFitColumn(ACol) / AutoFitColumns(ColMin, ColMax) and AutoFitRow(ARow) / AutoFitRows(RowMin, RowMax); TXLSXRange exposes AutoFitColumns and AutoFitRows that forward to the worksheet over the range's bounds. Width is estimated in Excel character units (Calibri 11pt baseline; ASCII counts 1, CJK characters count 2) with a 1.20 px-per-char correction and 1.0 padding, clamped to [8.43, 255.0]. Row height defaults to 15 pt and grows by 12.75 pt per embedded newline in the cell text. Wide ranges (EntireRow / EntireColumn) are clamped to the last cell-bearing row / column on the worksheet so AutoFit does not iterate to the Excel grid limits.
Version 2.18.1
- Added per-cell protection round-trip to the XLSX engine. TXLSXCell exposes new Locked: Boolean (default True) and FormulaHidden: Boolean (default False) convenience properties that mirror Excel's cell-protection model. They surface the pool index TXLSXCell.ProtectionIndex (1-based into the new workbook-level TXLSXProtections pool); writing the convenience property looks up or appends the (Locked, FormulaHidden) pair in the pool. Defaults (Locked=True, FormulaHidden=False) collapse to ProtectionIndex=0 so the pool stays empty for stock cells.
- Added TXLSXRange.SetLocked(ALocked) and TXLSXRange.SetFormulaHidden(AHidden) batch helpers so unlocking or hiding a whole range only does one pool lookup-or-add and applies the result to every cell in the rectangle.
- Added the matching writer / reader plumbing. SaveAs emits one cellXf with an inline <protection locked="N" hidden="M"/> child per workbook Protections entry (after the alignment block); Open parses <protection> back into Workbook.Protections and threads the new CellXfProtMap through ParseStylesXml and ParseWorksheetXml so per-cell ProtectionIndex round-trips alongside the existing font / fill / border / number-format / alignment indices.
Version 2.18.0
- Added TXLSXRange object for multi-cell access and batch operations on TXLSXWorksheet. New entry points: Worksheet.Range['A1:C5'], Worksheet.RCRange[r1,c1,r2,c2], Worksheet.UsedRange, Worksheet.EntireRow(r), Worksheet.EntireColumn(c). The returned range exposes SetValue / SetFormula / Clear / ClearAll / Merge / Unmerge / Offset / Resize plus per-cell style index setters (SetFontIndex / SetFillIndex / SetBorderIndex / SetNumberFormatIndex / SetAlignmentIndex) and a SetNumberFormat(Fmt) convenience that looks up or appends the format string in the workbook pool. TXLSXRange objects are owned by the worksheet and released on worksheet destruction; callers do not free them.
- Added TXLSXCell.NumberFormat: WideString convenience that pairs with the existing NumberFormatIndex. Reading returns the format string from the workbook pool; writing looks up or adds the string and updates NumberFormatIndex transparently. Requires the cell to be attached to a workbook (the standard flow through TXLSXWorkbook.Sheets.Add(...).Cells[r, c]).
- Added cell -> sheet -> workbook back-references so each TXLSXCell can resolve its owning workbook without external bookkeeping. TXLSXSheets, TXLSXWorksheet, and TXLSXCells gained internal Owner references wired through their constructors; TXLSXCell exposes a read-only Workbook property and an internal SetWorkbook used by TXLSXCells.GetItem when auto-creating a cell on access.
Version 2.17.42
- Fixed XLSX cell round-trip on reopen. Cells written by SaveAs were being dropped on Open, with HasCell(row, col) returning False and all cell-level state (value, formula, font index, fill index, border index, number format index) lost. The root cause was a spurious MoveToAttribute('r') call in the worksheet parser that flipped the reader's node type to attribute, which then made the subsequent IsEmptyElement check return True and shut off cell parsing for every <c> element with an r="" attribute (i.e. every cell). Removing the unnecessary cursor move restores text, numeric, formula, and date cell round-trip together.
- Fixed XLSX style index restore on reopen. Custom font, fill, border, and number-format indices on cells were reading back as zero even after the matching pools (Workbook.Fonts/Fills/Borders/NumberFormats) round-tripped correctly. The cellXf parser tracked its position with a counter that only advanced on the </xf> end tag, but Excel commonly emits self-closing <xf .../> entries (no inner <alignment>), so the counter never moved past zero and every cellXf mapping clobbered the previous one. The parser now advances the counter on a self-closing <xf/> too, so per-cell FontIndex / FillIndex / BorderIndex / NumberFormatIndex round-trip.
- Fixed XLSX date cell round-trip. Writing a TDateTime to TXLSXCell.Value used to route through the shared-string pool with t="s" because Delphi's VarIsNumeric() returns False for varDate; SaveAs now writes the date serial number into <v> directly and applies the date cellXf, so date cells reopen as a varDate Variant instead of a localized string that the caller could not coerce back to TDateTime.
Version 2.17.41
- Fixed "zlib stream does not support seeking" error raised when reopening .xlsx files that contain embedded images or a VBA project (.xlsm). The image-reload path and the VBA-payload reload path both routed the inflated entry stream through TMemoryStream.CopyFrom(Source, 0), which internally rewinds Source to derive the byte count; the rewind is not legal on a forward-only Deflate stream. Both call sites now use a chunked Read-until-EOF helper, so .xlsx files with images or macros round-trip through SaveAs/Open without raising.
Version 2.17.40
- Added chart-sheet (full-page chart worksheet) support to the XLSX engine. TXLSXWorksheet exposes a new IsChartSheet flag plus a workbook-level Workbook.AddChartSheet(Name, ChartType, Title) helper that creates the sheet, flips the flag, and seeds Charts[0] with a sensible default anchor. SaveAs writes IsChartSheet sheets to xl/chartsheets/sheetN.xml with a dedicated rels file pointing at the shared drawing part, registers them as chartsheet content-types, and emits the chartsheet relationship in xl/_rels/workbook.xml.rels. Regular worksheets keep their existing xl/worksheets/sheetN.xml path. Open recognizes chartsheets via the relationship type but currently loads them as plain worksheets (their chart data still arrives via the shared chart parts).
Version 2.17.39
- Added collapsed row / collapsed column round-trip for outline groups in the XLSX engine. TXLSXWorksheet exposes RowCollapsed[Row] and ColCollapsed[Col] Boolean properties along with SetRowCollapsed / SetColCollapsed / ClearRowCollapsed / ClearColCollapsed helpers. SaveAs emits collapsed="1" on the matching <row> and <col> entries; Open parses the attribute back. Together with the existing outlineLevel and hidden attributes, this lets the XLSX engine round-trip a fully collapsed grouping hierarchy.
Version 2.17.38
- Added hidden row / hidden column round-trip to the XLSX engine. TXLSXWorksheet exposes RowHidden[Row] and ColHidden[Col] Boolean properties along with SetRowHidden / SetColHidden / ClearRowHidden / ClearColHidden helpers. SaveAs emits hidden="1" on the matching <row> and <col> entries; Open parses the attribute back into the worksheet so hidden rows / columns survive the round-trip without forcing a custom width or outline level.
Version 2.17.37
- Added worksheet tab color round-trip to the XLSX engine. TXLSXWorksheet exposes TabColor (ARGB) and TabColorIsAuto properties. SaveAs emits a <sheetPr><tabColor rgb="..."/></sheetPr> block as the first child of <worksheet> when the tab color is set; Open parses <tabColor> back into the worksheet so colored sheet tabs survive the round-trip. Default workbooks stay free of the extra block (TabColorIsAuto = True).
Version 2.17.36
- Added cell alignment round-trip to the XLSX engine. New TXLSXHorizontalAlignment and TXLSXVerticalAlignment token enums, TXLSXAlignment class (Horizontal, Vertical, WrapText, ShrinkToFit, Indent, TextRotation), and a workbook-level TXLSXAlignments collection (Workbook.Alignments). TXLSXCell now carries an AlignmentIndex (1-based into Workbook.Alignments). SaveAs appends one cellXf with an inline <alignment/> child per workbook alignment entry; Open reads <alignment> attributes back into Workbook.Alignments and maps the cellXf back to TXLSXCell.AlignmentIndex. ParseWorksheetXml and ParseStylesXml grew a CellXfAlignMap parameter to carry the mapping across the two phases.
Version 2.17.35
- Added the AES encryption (ECMA-376 Standard Encryption) API surface to the XLSX engine. TXLSXWorkbook exposes SaveAsEncrypted(FileName, Password) / OpenEncrypted(FileName, Password) / CanReadEncrypted(FileName) plus a typed EXlsxEncryptionNotImplemented exception. CanReadEncrypted recognizes the Microsoft Compound File Binary magic bytes ($D0 $CF $11 $E0 $A1 $B1 $1A $E1) so callers can detect encrypted archives before deciding how to open them; OpenEncrypted transparently falls back to plain Open for unencrypted .xlsx files. The AES-128/256 + SHA + OLE Compound File pipeline itself is not yet wired up — both write and read of actually-encrypted archives raise the typed exception until the follow-up release lands the algorithm. The exception type is deliberately distinct so existing callers can catch it and fall back to Worksheet.Protect / Workbook.ProtectWorkbook for the visual lock.
Version 2.17.34
- Performance: replaced the O(N^2) WideString concatenation that built each worksheet's cell rows with a TXLSWideStringBuilder. Large sheets (10k+ cells) now stay linear in memory and CPU on SaveAs. Added a new WriteWorksheetXmlStreaming helper and a TXLSXWorkbook.StreamingWrite opt-in flag — when enabled, SaveAs no longer holds every worksheet's XML in a sheetXmls cache at the same time; each worksheet is built, written, and dropped before moving to the next. sharedStrings.xml is emitted last so the streaming path still produces a fully-populated SST. Default behavior unchanged.
Version 2.17.33
- Added cell-range operations to the XLSX engine. TXLSXWorksheet exposes ClearRange(R1, C1, R2, C2), CopyRange(SrcR1, SrcC1, SrcR2, SrcC2, DstR, DstC) for in-sheet copy, CopyRangeTo(... TargetSheet, DstR, DstC) for cross-sheet copy, and MoveRange(SrcR1, SrcC1, SrcR2, SrcC2, DstR, DstC) for cut-and-paste. Each operation duplicates the cell value, formula, four style indices, and any rich-text payload; MoveRange skips overlap with the destination so partial overlaps still preserve their copied values. TXLSXCells also gains a Remove(Row, Col) helper.
Version 2.17.32
- Added internal hyperlink anchors to the XLSX engine. TXLSXHyperlink now carries a Location property (e.g. "Sheet2!A1") and an IsInternal helper; TXLSXHyperlinks exposes AddInternal, and TXLSXWorksheet adds three AddHyperlinkToCell overloads. SaveAs emits internal hyperlinks with the inline location="" attribute and skips the worksheet rels entry; Open recognizes location="" hyperlinks and routes them back through AddInternal. External URL hyperlinks continue to flow through the rels file unchanged.
Version 2.17.31
- Added rich-text round-trip to the XLSX engine. New TXLSXRichTextRun class (per-run Name / Size / Bold / Italic / Strikethrough / Underline / Color) and TXLSXRichText container that exposes AddRun / AddRunText / Clear / PlainText. TXLSXCell now carries an owned RichText property; SaveAs writes the cell as a shared-string with multiple <r>/<rPr>/<t> runs and Open rebuilds the TXLSXRichText from the parsed SST entry, with the cell's Variant Value still surfacing the concatenated plain text for callers that ignore formatting.
Version 2.17.30
- Added chart round-trip to the XLSX engine. New TXLSXChartType enum (column / bar / line / pie), TXLSXChartSeries (Name, CategoriesRange, ValuesRange), and TXLSXChart (ChartType, Title, axis titles, From/To anchor cells, Series) classes plus a per-worksheet TXLSXCharts collection (Worksheet.Charts). TXLSXWorksheet exposes AddChart(Type, Title, FromRow, FromCol, ToRow, ToCol). SaveAs emits xl/charts/chartN.xml (workbook-global numbering) and binds it to a twoCellAnchor / graphicFrame inside the shared xl/drawings/drawingN.xml; Open parses both the anchor and the chart definition back into the collection. Chart titles, axis titles, series names and source ranges round-trip.
Version 2.17.29
- Fixed an EListError "Operation not allowed on sorted list" raised by SetColWidth, SetRowHeight, SetRowOutlineLevel, and SetColOutlineLevel. Each of those four TStringList fields was created with Sorted=True but written through the Values[Name] := X setter, which RAD Studio's RTL refuses on a sorted list. The lists are now created unsorted; lookups continue to go through IndexOfName, which is fine at typical per-sheet column / row counts.
Version 2.17.28
- Fixed TZipArchive.Exists, which was a stub that always returned false. The XLSX Open path uses zip.Exists(...) to gate every part read, so every previously-claimed round-trip (cells, styles, comments, drawings, doc props, defined names, protection, VBA, etc.) silently dropped its content on read. Exists now delegates to the existing Fcd.Entries.Exists lookup, matching the OpenFile / CreateFile pattern.
Version 2.17.27
- Added VBA project preservation to the XLSX engine. TXLSXWorkbook exposes a VbaProject byte payload plus LoadVbaProjectFromFile(FileName), ClearVbaProject, and HasVbaProject helpers. SaveAs writes the bytes to xl/vbaProject.bin, registers the .bin content-type, switches the workbook content-type to the macro-enabled variant, and adds a vbaProject relationship to the workbook rels file; Open reads xl/vbaProject.bin back into the property. The bytes are not parsed or modified, so existing .xlsm projects round-trip exactly. Note: macro-enabled workbooks must be saved with a .xlsm extension for Excel to enable macros.
Version 2.17.26
- Added workbook and sheet protection to the XLSX engine. TXLSXWorksheet exposes Protect / Protect(Password) / UnProtect plus IsProtected and SheetProtectHash; TXLSXWorkbook adds ProtectWorkbook (with optional password, LockStructure, LockWindows flags) and UnProtectWorkbook plus IsWorkbookProtected / WorkbookProtectHash / LockStructure / LockWindows. SaveAs emits <sheetProtection> on each protected worksheet and <workbookProtection> on protected workbooks; Open parses both blocks back into the API. Passwords are stored as the standard 4-hex legacy hash for round-trip fidelity (the original plaintext is not recoverable).
Version 2.17.25
- Added document-property round-trip to the XLSX engine. TXLSXWorkbook now exposes Title, Author, Subject, Keywords, Description, Category, LastModifiedBy, Created, Modified, Company, Application, and AppVersion properties. SaveAs emits docProps/core.xml and docProps/app.xml (and registers them in [Content_Types].xml + _rels/.rels) when any property is touched; Open parses both files back so authors, modification stamps, and application metadata survive a save-and-reopen.
Version 2.17.24
- Added Excel-style table round-trip to the XLSX engine. New TXLSXTable class (Id, Name, DisplayName, Range, Columns, StyleName, ShowFirstColumn / ShowLastColumn / ShowRowStripes / ShowColumnStripes) and a per-worksheet TXLSXTables collection. TXLSXWorksheet exposes Tables plus AddTable(Name, Range, ColumnNames). SaveAs emits xl/tables/tableN.xml (workbook-global numbering), updates worksheet rels with table relationships, adds <tableParts> to the worksheet, and registers the table content-type. Open parses table relationships and tableN.xml back into the worksheet so styled table bands survive a save-and-reopen.
Version 2.17.23
- Added auto-filter round-trip to the XLSX engine. TXLSXWorksheet exposes an AutoFilterRange property plus SetAutoFilter(Row1, Col1, Row2, Col2) / SetAutoFilter(Range) / ClearAutoFilter helpers. SaveAs emits <autoFilter ref="..."/> after the merge-cells block when the range is non-empty; Open parses the ref back into the property so the filter band on a worksheet survives a save-and-reopen.
Version 2.17.22
- Added page-setup round-trip to the XLSX engine. TXLSXWorksheet exposes Margin{Left,Right,Top,Bottom,Header,Footer}, PageLandscape, PaperSize, PageScale, FitToWidth, FitToHeight, HeaderText, and FooterText properties plus SetPageMargins(L, R, T, B[, H, F]) convenience overloads. SaveAs emits the corresponding <pageMargins>, <pageSetup>, and <headerFooter> blocks between hyperlinks and drawings; Open parses them back. Default-only worksheets stay untouched (PageSetupTouched flag gates output).
Version 2.17.21
- Added freeze-pane round-trip to the XLSX engine. TXLSXWorksheet exposes FreezePane(Col, Row) / UnfreezePane methods plus read-only FreezeCol and FreezeRow properties. SaveAs writes a <sheetViews>/<pane state="frozen"> block before <sheetData> with the matching activePane and selection; Open parses <pane state="frozen"> back into the freeze position so frozen-top/left/corner layouts survive a save-and-reopen.
Version 2.17.20
- Added row and column outline (grouping) levels to the XLSX engine. TXLSXWorksheet exposes RowOutlineLevel[Row] and ColOutlineLevel[Col] indexed properties plus SetRowOutlineLevel / HasRowOutlineLevel / ClearRowOutlineLevels and the matching column helpers. SaveAs emits outlineLevel attributes on <row> entries and merged <col> entries (combined with custom widths when present); Open parses both attributes back into the worksheet so grouped row/column hierarchies survive the round-trip.
Version 2.17.19
- Added conditional formatting and data validation round-trip to the XLSX engine. New TXLSXCfOperator enum and TXLSXConditionalFormat class (Range, Op, Formula1, Formula2) plus a TXLSXConditionalFormats collection on each worksheet (Worksheet.ConditionalFormats). New TXLSXDataValidationType, TXLSXDvOperator, TXLSXDataValidation (Range, ValidationType, Op, Formula1, Formula2, AllowBlank, ShowInputMessage, ShowErrorMessage), and a TXLSXDataValidations collection (Worksheet.DataValidations) with an AddList(Range, Items) shortcut for the common dropdown case. TXLSXWorksheet exposes AddConditionalFormat and AddDataValidation / AddListValidation helpers. SaveAs writes <conditionalFormatting> blocks (with cfRule type="cellIs") and a <dataValidations> block after sheetData; Open parses both blocks back into the collections.
Version 2.17.18
- Added defined-name round-trip to the XLSX engine. New TXLSXDefinedName class (Name, Formula, SheetIndex) and a workbook-level TXLSXDefinedNames collection (Workbook.DefinedNames) with Add(Name, Formula[, SheetIndex]) overloads and IndexOfName lookup. SaveAs emits a <definedNames> block in xl/workbook.xml; entries with SheetIndex >= 0 carry a localSheetId attribute (sheet-scoped) and entries with SheetIndex = -1 are workbook-scoped. Open parses <definedName> elements back into the collection so saved-and-reopened workbooks keep their named ranges intact.
Version 2.17.17
- Added image round-trip to the XLSX engine. New TXLSXImageFormat enum (png, jpeg, gif, bmp), TXLSXImage class (Row/Col anchor, WidthEMU/HeightEMU, Format, Data), and a per-worksheet TXLSXImages collection. TXLSXWorksheet exposes Images plus AddImage(Row, Col, Data, Format) and AddImageFromFile(Row, Col, FileName) helpers. SaveAs writes the image bytes into xl/media/imageN.<ext>, generates xl/drawings/drawingN.xml with a oneCellAnchor per image (and a matching _rels file), registers content types and worksheet/drawing relationships, and emits a <drawing r:id="..."/> reference inside the worksheet. Open parses drawingN.xml + drawing rels + media to round-trip images back into Worksheet.Images.
Version 2.17.16
- Added custom number-format round-trip to the XLSX engine. New TXLSXNumberFormat class and a workbook-level TXLSXNumberFormats collection (Workbook.NumberFormats) deduplicate format codes via Add(FormatCode). TXLSXCell exposes a new NumberFormatIndex property; SaveAs emits a <numFmts> block in xl/styles.xml (custom ids starting at the OOXML-reserved base 164) and pairs each format with a dedicated cellXf, Open parses custom numFmts back and reverses the cellXf->numFmtId map into NumberFormatIndex on read. Precedence on conflict: FormatIndex > FontIndex > FillIndex > BorderIndex > NumberFormatIndex.
Version 2.17.15
- Added cell-border round-trip to the XLSX engine. New TXLSXBorderStyle enum, TXLSXBorderEdge class (Style + Color + ColorIsAuto for each side), TXLSXBorder class with Left/Right/Top/Bottom/Diagonal edges plus SetAll(Style[, Color]) helpers, and a workbook-level TXLSXBorders collection (Workbook.Borders) with a Borders.AddBox(Style[, Color]) shortcut. TXLSXCell exposes a new BorderIndex property; SaveAs emits each border into xl/styles.xml and pairs it with a dedicated cellXf, Open parses borders back and translates the cellXf->borderId map into BorderIndex on read. Precedence remains FormatIndex > FontIndex > FillIndex > BorderIndex when several are set on the same cell.
Version 2.17.14
- Added cell-fill round-trip to the XLSX engine. New TXLSXFillPattern enum and TXLSXFill class describe patternType, fgColor, and bgColor; the workbook-level TXLSXFills collection is reachable via Workbook.Fills, with a Fills.AddSolid(color) convenience for the common solid-color case. TXLSXCell exposes a new FillIndex property; SaveAs emits each fill into xl/styles.xml and pairs it with a dedicated cellXf, Open parses them back and reverses cell s attributes into FillIndex on read. FontIndex still takes precedence when both are set.
Version 2.17.13
- Completed the XLSX font round-trip. Open now parses xl/styles.xml back into Workbook.Fonts and a cellXf->fontId table; each cell's s attribute is mapped back into TXLSXCell.FontIndex so saved-and-reopened workbooks preserve custom fonts (Name, Size, Bold, Italic, Strikethrough, Underline, Color).
Version 2.17.12
- Added a TXLSXFont class plus a Workbook.Fonts collection so XLSX cells can pick a custom font. TXLSXCell carries a new FontIndex property; SaveAs emits the fonts list in xl/styles.xml and one cellXf per font, then references the matching cellXf from each cell's s attribute. Open round-trip of FontIndex is planned for the next release.
Version 2.17.11
- Added row-height round-trip to the XLSX engine. TXLSXWorksheet exposes a RowHeight[Row] property plus SetRowHeight, HasRowHeight, and ClearRowHeights helpers. SaveAs writes the ht and customHeight="1" attributes on each <row> with a customized height (including rows that only carry a height, with no cell data); Open reads the ht attribute back into the row-height map.
Version 2.17.10
- Added column-width round-trip to the XLSX engine. TXLSXWorksheet exposes a ColWidth[Col] property along with SetColWidth, HasColWidth, and ClearColWidths helpers. SaveAs emits a <cols> block with one <col> entry per customized column; Open replays <col min/max/width> entries back into the worksheet so unmodified widths fall back to the Excel default.
Version 2.17.9
- Added cell-comment round-trip to the XLSX engine. TXLSXWorksheet exposes a Comments collection and AddComment(Row, Col, Text[, Author]) overloads. SaveAs emits xl/commentsN.xml (with deduplicated author list) and a companion xl/drawings/vmlDrawingN.vml so Excel can render the comment balloons; Open reads commentsN.xml back into the collection. Worksheet rels, content types, and <legacyDrawing> wiring are handled automatically.
Version 2.17.8
- Added hyperlink round-trip to the XLSX engine. TXLSXWorksheet exposes a Hyperlinks collection and AddHyperlink(Row, Col, Url[, Display[, Tooltip]]) overloads. SaveAs writes a <hyperlinks> block in each worksheet and emits a matching xl/worksheets/_rels/sheetN.xml.rels file with the external URL targets; Open reads the worksheet rels first to resolve <hyperlink r:id="..."/> entries into URLs in the collection.
Version 2.17.7
- Added merged-cell round-trip to the XLSX engine. TXLSXWorksheet exposes a MergedCells collection and a MergeCells(R1, C1, R2, C2) convenience method that mirror the BIFF facade style. SaveAs emits a <mergeCells> block after sheetData, and Open parses <mergeCell ref="A1:B2"/> entries back into the collection.
Version 2.17.6
- The XLSX engine now round-trips date values and formulas. TDateTime cell values are serialized as Excel serial numbers and tagged with a built-in date cellXf so Excel renders them as dates; on Open, cells whose style references the date cellXf are decoded back to TDateTime variants. TXLSXCell exposes a new Formula property; SaveAs writes the formula as a <f> child element and Open parses <f> text back into the cell.
Version 2.17.5
- Added a styles.xml scaffold to the XLSX engine. SaveAs now emits a minimum-valid xl/styles.xml with default fonts, fills, borders, cellStyleXfs, and cellXfs entries, plus the corresponding content-type and workbook-relationship registrations. Cells carry a FormatIndex property and emit the s="N" cell attribute when non-zero, and Open reads s="N" back into FormatIndex. Concrete style descriptors (fonts, fills, borders, cellXfs as workbook-level collections) will be added in follow-up commits.
Version 2.17.4
- Added shared strings (SST) support to the XLSX engine. SaveAs builds a deduplicated string table and emits xl/sharedStrings.xml, replacing inline string emission. Open reads xl/sharedStrings.xml first and resolves t="s" cell references through the SST. Inline string cells from third-party XLSX files are still accepted on Open.
Version 2.17.3
- Replaced the cell record array on TXLSXWorksheet with TXLSXCell objects and a TXLSXCells collection. The cell-access call site now reads Worksheet.Cells.Item[Row, Col].Value, matching the IXLSCells / IXLSRange shape on the BIFF facade. Worksheet.Cells.HasCell / Cells.Count / Cells.CellByIndex / Cells.Clear are also available.
Version 2.17.2
- Introduced the TXLSXSheets collection class on TXLSXWorkbook, mirroring the IXLSWorkSheets collection on the BIFF facade. Code now reads Workbook.Sheets.Add / Workbook.Sheets.Count / Workbook.Sheets[i] / Workbook.Sheets.IndexByName instead of the previous Workbook.AddSheet / Workbook.SheetCount / Workbook.Sheet[i] methods.
Version 2.17.1
- Renamed TXLSXWorkbook.SaveToFile to SaveAs and TXLSXWorkbook.LoadFromFile to Open, matching the existing IXLSWorkBook naming style. Added file-format and password overloads so the XLSX facade reads the same way as the BIFF facade. AddSheet now offers a no-argument overload that generates a default sheet name.
Version 2.17.0
- TXLSXWorkbook.SaveAs produces a minimum OOXML .xlsx archive with cell values (numbers, booleans, and inline strings). The archive contains content types, root relationships, workbook + relationships, and a worksheet per sheet.
- TXLSXWorkbook.Open reads back the minimum OOXML .xlsx archive — sheet names from xl/workbook.xml and cell values from xl/worksheets/sheetN.xml. Shared strings, styles, dates, and formulas are not yet supported.
- Added OOXML namespace, content-type, and relationship constants plus reference / parsing / escape helpers (XlsxColumnLabel, XlsxCellRef, XlsxColumnIndex, XlsxParseCellRef, XlsxParseRangeRef, XlsxEscapeText, XlsxEscapeAttr) for use by future XLSX features.
Version 2.16.4
- The XLSX facade unit (lxHandleX) now defines its own workbook and worksheet types (TXLSXWorkbook, TXLSXWorksheet), independent of the BIFF facade. Save and load entry points are reserved; the OOXML wiring will be filled in a follow-up release.
- Added a developer reference topic for the new XLSX facade, describing how lxHandle and lxHandleX coexist and which helper units they share.
Version 2.16.3
- Introduced
lxHandleX, a dedicated XLSX facade unit that lives alongside the existinglxHandleBIFF facade. The two units share the same underlying infrastructure (style pools, XML utilities, ZIP archive) but expose independent class hierarchies:lxHandleownsTXLSWorkBookand the classic XLS/HTML/RTF/CSV paths;lxHandleXwill ownTXLSXWorkbookand all XLSX-specific APIs. Three method stubs accidentally added tolxHandle(SetCodePage,GetColumnXFIndex,GetRowXFIndex) were removed to keep the BIFF facade’s public API identical to v2.15.0. Existing code thatuses lxHandlerequires no changes.
Version 2.16.2
- Added three internal data-structure units completing the XLSX engine foundation:
lxAvlTreeprovides an O(log N) AVL tree (TAVLTree/TAVLNode) for sorted lookups;lxColsandlxRowsare column and row info containers with fine-grained deduplication and index rewriting, used by the XLSX engine’s per-sheet column-width and row-height maps. All three are dormant in this release and are activated in v2.17.0 when the XLSX read/write engine is first enabled.
Version 2.16.1
- Added six internal XML and ZIP utility units that complete the XLSX engine’s infrastructure layer:
lxWStream(wide-character file stream with file-existence check and AnsiString write helper);lxCacheStream(buffered stream wrapper that reduces underlying I/O calls);lxZlibStream(forward-only deflate/inflate stream, independent of the existingloZlibBIFF encryption binding);lxXmlReader(SAX-style pull reader for high-speed XML parsing);lxXmlWriter(streaming XML writer with element-name reuse);lxZipArchive(central-directory ZIP reader/writer with per-entry deflate streams). Together with the seven units from v2.16.0 these form the complete internal foundation for the XLSX engine.
Version 2.16.0
- Added seven internal style-infrastructure units as the foundation for the upcoming XLSX engine:
lxHashTable(generic hash tables with integer, WideString, and object-key variants);lxKeyList(object-key list with fast lookup and index rewriting);lxStyleColor(Office theme color and palette slot manager);lxStyleFont,lxStyleFill, andlxStyleBorder(deduplicated pools for font, fill, and border styles covering all 12 BIFF line types and XLSX style mappings);lxStyleXf(aggregates all five style pools with a number-format list into a single XF record). All seven units are dormant in this release and do not affect existing XLS paths.
Version 2.15.1
- Added
lxRgbcolor utility helpers:RGBtoHLS,HLStoRGB,RGBTint, andGetRGBTintconvert between the RGB and HLS color spaces (0–240 HLS range). These underpin theme-color tint calculations, color-scale conditional formatting, and palette interpolation in XLSX output. - Added
lxStrBuilderhigh-performance string builders:TXLSStringBuilder(AnsiChar) andTXLSWideStringBuilder(WideChar) use pre-allocated, doubling-growth buffers to eliminate the per-concatenation memory reallocation of standard Pascal strings. The AnsiChar variant drives the XLSX XML serialization path; the WideChar variant handles wide-string assembly in the XLSX parser.
Version 2.15.0
- Reworked the HtmlHelp reference into a web-ready structure with dedicated topic, asset, script, style, and source folders.
- Added a browser-friendly help home page and contents navigation page, with unified headers, topic shortcuts, and footers.