--- source_files: - Common/DTS.Common.SerializationPlus/XLSX/Excel.File.cs - Common/DTS.Common.SerializationPlus/XLSX/Excel.File.Writer.cs generated_at: "2026-04-16T03:30:27.540490+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "6f0085250a497f9b" --- # XLSX Export Module Documentation ## 1. Purpose This module implements XLSX (Excel) file export functionality for test data within the DTS.Serialization framework. It extends `Serialization.File` and implements `IWritable` to provide structured export of `Test` objects to Excel spreadsheets. The module supports exporting engineering units (EU), millivolt (mV), and analog-to-digital converter (ADC) values based on configurable flags, and includes support for filtered/unfiltered data, time-based slicing (`Start`/`Stop`), and user-selected export headers. It uses the `DocumentFormat.OpenXml` library to generate compliant XLSX files, leveraging a template file (`XLSXExportTemplate.xlsx`) and performing two-pass writing: first for metadata/headers, then for numeric data using SAX-style streaming for performance. ## 2. Public Interface ### `DTS.Serialization.XLSX.File` - **`public File()`** Constructor that initializes the XLSX file handler with type name `"XLSX"`. - **`public IWriter Exporter { get; }`** Returns the `IWriter` instance used to write test data. Lazily instantiates a `Writer` on first access using `DefaultEncoding`. Throws a wrapped exception if instantiation fails. - **`public bool ExportADC { set; }`** Sets the `ExportADC` property on the underlying `Writer` instance. Controls whether raw ADC values are exported. - **`public bool ExportEU { set; }`** Sets the `ExportEU` property on the underlying `Writer` instance. Controls whether engineering units (EU) values are exported. Default is `true`. - **`public bool ExportMV { set; }`** Sets the `ExportMv` property on the underlying `Writer` instance. Controls whether millivolt (mV) values are exported. ### `DTS.Serialization.XLSX.File.Writer` - **`internal File WriterParent { get; }`** Reference to the owning `File` instance. - **`public bool ExportADC { get; set; }`** Gets or sets whether to export raw ADC values. - **`public bool ExportEU { get; set; }`** Gets or sets whether to export engineering units (EU) values. Default is `true`. - **`public bool ExportMv { get; set; }`** Gets or sets whether to export millivolt (mV) values. - **`public double Start { get; set; }`** Start time (in seconds) for data slicing. - **`public double Stop { get; set; }`** Stop time (in seconds) for data slicing. - **`public bool Filtered { get; set; }`** Indicates whether filtered data should be exported. - **`public List ExportHeaders { get; set; }`** List of user-selected headers to include in the export (FB 6410). Only headers with `IsSelected == true` are written. - **`public void Write(string pathname, string id, Test test, bool bFiltering, bool includeGroupNameInISOExport, double minStartTime, int dataCollectionLength)`** *Note: This method is declared but has no body in the provided source.* Likely a legacy or stub signature. - **`public void Write(string pathname, string id, string dataFolder, Test test, bool bFiltering, bool includeGroupNameInISOExport, FilteredData fd, Test.Module.Channel tmChannel, int channelNumber, BeginEventHandler beginEventHandler, CancelEventHandler cancelEventHandler, EndEventHandler endEventHandler, TickEventHandler tickEventHandler, ErrorEventHandler errorEventHandler, CancelRequested cancelRequested, double minStartTime, int dataCollectionLength)`** Main export method. Writes a `Test` object to an XLSX file at `pathname`. Performs: - Template file copy and OpenXML document opening. - Header row indexing via `_headerRowLineIndex` (FB 6410). - Metadata writing (test date/time, ID, description, channel info, scaling, filter settings). - Data scaling via `GetDataScaler` and precomputation of scalers in `_aicToScaler`. - Optional filtering of EU data using `SaeJ211Filter`. - Two-pass writing: first pass writes headers/metadata; second pass streams data using SAX-style `OpenXmlReader`/`OpenXmlWriter`. - Interpolation for resampling channels with different sample rates. - Progress updates every 1000 samples (`UPDATE_INTERVAL`). - Cleanup: unsets channel data, logs errors, invokes event handlers. - **`public static Common.DAS.Concepts.DataScaler GetDataScaler(Test.Module.AnalogInputChannel currentAnalogChannel)`** Constructs and configures a `DataScaler` instance from an `AnalogInputChannel`, including linearization, scaling factors, zero methods, digital channel settings (FB 14469), and excitation voltage properties. - **`protected Cell GetCell(Worksheet worksheet, string xColumn, uint rowIndex, bool bLookForCell = true)`** Retrieves or inserts a cell at the specified location. Uses `_rowIndexToRow` cache for performance. - **`private static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart, string cellReference, bool bLookForCell)`** Inserts a cell into the worksheet, maintaining OpenXML cell ordering by `CellReference`. - **`private void WriteTime(WorksheetPart ws, string column, uint row, DateTime time, SharedStringTablePart sharedString)`** Writes a time value as a shared string with number format ID 14 (time format). - **`private void WriteTime(WorksheetPart ws, string column, string headerName, DateTime time, SharedStringTablePart sharedString)`** Overload that writes to the row index associated with `headerName` (FB 6410). - **`private void WriteDate(WorksheetPart ws, string column, uint row, DateTime date)`** Writes a date as an OLE Automation date (OADate) with number format. - **`private void WriteDate(WorksheetPart ws, string column, string headerName, DateTime date)`** Overload that writes to the row index associated with `headerName` (FB 6410). - **`private void WriteDouble(WorksheetPart ws, string column, uint row, double value)`** Writes a numeric value directly to a cell. - **`private void WriteDouble(WorksheetPart ws, string column, string headerName, double value)`** Overload that writes to the row index associated with `headerName` (FB 6410). - **`private void WriteString(WorksheetPart ws, string column, uint row, SharedStringTablePart sharedStringTablePart, string value)`** Writes a string as a shared string. - **`private void WriteString(WorksheetPart ws, string column, string headerName, SharedStringTablePart sharedStringTablePart, string value)`** Overload that writes to the row index associated with `headerName` (FB 6410). Skips duplicate entries via `_alreadyEnteredHeader`. - **`private int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)`** Adds or retrieves a shared string index. Uses `_stringLookup` dictionary for O(1) lookup. - **`private static string GetColumn(int index)`** Converts a zero-based column index to Excel column letters (e.g., `0 → "B"`). Starts at column B (index 0 → "B"). - **`private void AddStyleSheet(SpreadsheetDocument sp)`** Adds a date/time number format (ID 14) to the stylesheet and caches its index in `_dateFormatIndex`. - **`private void InsertRow(WorksheetPart worksheetPart)`** Inserts an empty row after the last row in the worksheet. - **`private void UpdateProgress(double dValue, TickEventHandler tickEventHandler)`** Invokes the progress event handler if non-null. - **`internal Writer(File fileType, int encoding)`** Constructor initializing `Writer` base class, setting default export flags (`ExportEU = true`, others `false`), and storing `WriterParent`. - **`public void Initialize(...)`** *Note: This method is declared but has no body in the provided source.* Likely a stub or unused legacy method. ## 3. Invariants - **Export flags are mutually exclusive in practice**: While `ExportADC`, `ExportEU`, and `ExportMV` are independently settable, the current implementation writes only EU data by default and does not appear to conditionally include ADC/mV in the final data rows (only scaling factors are written regardless of flags). The flags control writer behavior but may not yet fully gate data output. - **Header row indexing is precomputed**: `_headerRowLineIndex` is built before writing data and must remain consistent during the export. Headers not in this dictionary are skipped. - **Channel ordering is deterministic**: Channels are sorted by `AbsoluteDisplayOrder`, then by `Number` to ensure reproducible column order. - **Data slicing respects `Start`/`Stop`**: Pre- and post-trigger sample counts are clamped to `Start * SampleRateHz` and `Stop * SampleRateHz`, respectively. - **Sample rate resampling uses ceiling**: Channels with lower sample rates are resampled using `rate = ceil(maxSampleRate / channelSampleRate)`. - **Interpolation is linear**: When `step > 0`, values are interpolated using a linear increment based on neighboring samples. - **Shared string table is cached**: `_stringLookup` is initialized once per export and reused for performance. - **Row caching is per-export**: `_rowIndexToRow` is cleared at the start of `Write` and reused within a single export. ## 4. Dependencies ### External Dependencies - **`DocumentFormat.OpenXml`**: Core library for XLSX manipulation. - **`DTS.Common.Enums` / `DTS.Common.Enums.Sensors`**: For `SensorConstants`, `IsoViewMode`, and digital channel enums. - **`DTS.Common.Interface.ExportData`**: For `IExportHeader` and `FilteredData`. - **`DTS.Common.SerializationPlus`**: Base classes `Serialization.File`, `Writer`, `IWriter`, `BeginEventHandler`, etc. - **`DTS.Common.Utilities.Logging`**: For `APILogger`. - **`DTS.Slice.Control`**: For `SaeJ211Filter` and `UseLegacyTDCSoftwareFiltering`. ### Internal Dependencies - **`DTS.Common.Concepts.DataScaler`**: Used for channel scaling and linearization. - **`DTS.Common.DAS.Concepts.DataScaler`**: Full namespace path used in `GetDataScaler`. - **`DTS.Test`**: `Test`, `Test.Module.Channel`, `Test.Module.AnalogInputChannel`, `Test.Module.ChannelInfo`, `Test.Module.Module`. - **`DTS.Common.Enums.IsoViewMode`**: Controls ISO/User code display in header. - **`XLSXExportHeaderLine`**: Enum-like class (likely static class with `GetDescription()` methods) defining header names. ### Inferred Dependencies - **`XLSXExportTemplate.xlsx`**: Must exist at runtime relative to `AppDomain.CurrentDomain.BaseDirectory\ReportTemplates\`. - **`UseLegacyTDCSoftwareFiltering`**: Global/static flag used in filtering logic (not defined in source). ## 5. Gotchas - **`ExportADC`, `ExportEU`, `ExportMV` flags may not fully gate data output**: While setters exist and are passed to the `Writer`, the `Write` method only writes EU data to the data rows. ADC/mV export may be incomplete or unimplemented. - **`Write(string pathname, string id, Test test, ...)` overload has no body**: Likely a stub or legacy method; only the 14-parameter overload is implemented. - **`Initialize(...)` method has no body**: May be unused or intended for future implementation. - **`_rowIndexToRow` is a static field**: Shared across all `Writer` instances in the AppDomain. This could cause race conditions in multi-threaded export scenarios (though current code clears it per export). - **`_stringLookup` is not thread-safe**: Used without locking; safe only because `Write` is single-threaded per instance. - **`GetColumn` starts at index 0 → "B"**: Column A is skipped; likely intentional for header/data layout. - **`Filtered` flag affects both data filtering and filter name display**: When `Filtered == false`, the software filter name is written as `"NONE"` and cutoff frequency is omitted or set to `0`. - **FB 18024 and FB 6410 references**: Bug fixes embedded in comments (e.g., "FB 6410") indicate historical quirks around header row handling and filter display. - **FB 14469 fix for digital channels**: Digital channel properties (`Digital`, `DigitalMode`, `DigitalMultiplier`) must be explicitly set on the scaler; otherwise EU values may be incorrect. - **FB 14513 rounding error mitigation**: Uses `Decimal` for time offset calculations to avoid double-precision rounding issues. - **FB 14659 channel ordering fix**: Uses `AbsoluteDisplayOrder` (not display order) to ensure correct channel indexing. - **Performance optimizations removed**: GC calls were commented out due to performance impact (likely legacy 32-bit concern). - **Two-pass writing complexity**: First pass writes headers/metadata; second pass streams data. This is necessary for SAX-style writing but increases code complexity and potential for bugs. - **`minStartTime` vs. `dStartTime` alignment**: Time alignment logic (`channelOffsetStart`) is critical for correct sample indexing across channels with different start times.