mirror of
https://github.com/radzenhq/radzen-blazor.git
synced 2026-07-08 18:16:08 +00:00
Remove noise comments and non-public XML docs from spreadsheet code
Two sweeps over Radzen.Blazor/Documents/Spreadsheet and Radzen.Blazor/Spreadsheet: - Delete inline `//` comments that merely restate the adjacent code (e.g. "// Decrease column count" above `ColumnCount--;`, the "// Parse X" labels above the matching ParseX(...) calls). Comments explaining *why*, Excel/OOXML quirks, ordering requirements and trade-offs are kept. - Delete XML doc comments (`///`) on non-public declarations — private/internal members and the members of internal types (XlsxWriter/Reader, CsvWriter/Reader, the formula lexer/parser, the Functions classes, AutofillCommand, etc.). Docs on the public API surface are untouched (the build enforces this via CS1591). Comment-only changes; no behavior change. Build clean, 1477 spreadsheet tests pass.
This commit is contained in:
committed by
Vladimir Enchev
parent
c03142df75
commit
dc699e9518
@@ -16,9 +16,6 @@ public class Axis(double size, int count)
|
||||
/// </summary>
|
||||
public double Size => size;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of items along this axis.
|
||||
/// </summary>
|
||||
private int count = count;
|
||||
|
||||
/// <summary>
|
||||
@@ -164,11 +161,6 @@ public class Axis(double size, int count)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts custom sizes and hidden state up after a row/column is deleted.
|
||||
/// Entries at the deleted index are removed; entries above are unchanged;
|
||||
/// entries below are decremented by one.
|
||||
/// </summary>
|
||||
internal void ShiftUp(int deletedIndex)
|
||||
{
|
||||
var newData = new Dictionary<int, double>();
|
||||
@@ -207,10 +199,6 @@ public class Axis(double size, int count)
|
||||
hidden.UnionWith(newHidden);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts custom sizes and hidden state down after rows/columns are inserted.
|
||||
/// Entries at or after the insert point are incremented by count.
|
||||
/// </summary>
|
||||
internal void ShiftDown(int fromIndex, int count)
|
||||
{
|
||||
var newData = new Dictionary<int, double>();
|
||||
@@ -247,14 +235,8 @@ public class Axis(double size, int count)
|
||||
hidden.UnionWith(newHidden);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the indices of all items with a custom (non-default) size.
|
||||
/// </summary>
|
||||
internal IEnumerable<int> GetCustomSizedIndices() => data.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the indices of all hidden items.
|
||||
/// </summary>
|
||||
internal IEnumerable<int> GetHiddenIndices() => hidden;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -266,9 +266,6 @@ public class Cell
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ValidationErrors { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Clears the validation errors for the cell.
|
||||
/// </summary>
|
||||
internal void ClearValidationErrors()
|
||||
{
|
||||
ValidationErrors = [];
|
||||
|
||||
@@ -402,9 +402,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
/// </summary>
|
||||
public bool IsError => Type == CellDataType.Error;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this cell data is equal to another cell data.
|
||||
/// </summary>
|
||||
internal bool IsEqualTo(CellData other)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
@@ -427,10 +424,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
return ((IComparable)Value!).CompareTo(other.Value) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this cell data is less than another cell data.
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
internal bool IsLessThan(CellData other)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
@@ -443,10 +436,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
return compareResult < 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this cell data is greater than another cell data.
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
internal bool IsGreaterThan(CellData other)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
@@ -459,10 +448,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
return compareResult > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this cell data is less than or equal to another cell data.
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
internal bool IsLessThanOrEqualTo(CellData other)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
@@ -475,10 +460,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
return compareResult <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this cell data is greater than or equal to another cell data.
|
||||
/// </summary>
|
||||
/// <param name="other"></param>
|
||||
internal bool IsGreaterThanOrEqualTo(CellData other)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
@@ -528,11 +509,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
/// </summary>
|
||||
public static CellData FromError(CellError error) => new(error, CellDataType.Error);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this cell data matches the specified criteria.
|
||||
/// </summary>
|
||||
/// <param name="criteria">The criteria to match against</param>
|
||||
/// <returns>True if this cell matches the criteria, false otherwise</returns>
|
||||
internal bool MatchesCriteria(CellData criteria)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(criteria);
|
||||
@@ -568,7 +544,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
}
|
||||
}
|
||||
|
||||
// Default comparison
|
||||
return IsEqualTo(criteria);
|
||||
}
|
||||
|
||||
@@ -623,7 +598,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse the value
|
||||
if (!double.TryParse(valueStr, out var numericValue))
|
||||
{
|
||||
return false;
|
||||
@@ -644,7 +618,6 @@ public class CellData : IComparable, IComparable<CellData>
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform the comparison
|
||||
return operatorStr switch
|
||||
{
|
||||
">" => cellValue > numericValue,
|
||||
|
||||
@@ -113,16 +113,8 @@ public class CellStore(Worksheet sheet)
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all populated cells in the store without creating new cells.
|
||||
/// </summary>
|
||||
internal IEnumerable<Cell> GetPopulatedCells() => data.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Removes all empty cells from the store, freeing memory.
|
||||
/// A cell is empty when it has no value, formula, format, or hyperlink.
|
||||
/// </summary>
|
||||
/// <returns>The number of cells removed.</returns>
|
||||
internal int Compact()
|
||||
{
|
||||
var keysToRemove = new List<(int row, int column)>();
|
||||
@@ -143,20 +135,10 @@ public class CellStore(Worksheet sheet)
|
||||
return keysToRemove.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of populated cells in the store.
|
||||
/// </summary>
|
||||
internal int PopulatedCount => data.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a cell exists at the specified position without creating it.
|
||||
/// </summary>
|
||||
internal bool HasCell(int row, int column) => data.ContainsKey((row, column));
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cells at the specified row and shifts cells from higher rows up by one.
|
||||
/// Only touches populated cells, making this O(populated) instead of O(rows × columns).
|
||||
/// </summary>
|
||||
internal void ShiftRowsUp(int deletedRow)
|
||||
{
|
||||
var toRemove = new List<(int row, int column)>();
|
||||
@@ -188,10 +170,6 @@ public class CellStore(Worksheet sheet)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts all cells at or after the specified row down by the given count.
|
||||
/// Only touches populated cells, making this O(populated) instead of O(rows × columns).
|
||||
/// </summary>
|
||||
internal void ShiftRowsDown(int fromRow, int count)
|
||||
{
|
||||
var toRekey = new List<((int row, int column) key, Cell cell)>();
|
||||
@@ -219,10 +197,6 @@ public class CellStore(Worksheet sheet)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all cells at the specified column and shifts cells from higher columns left by one.
|
||||
/// Only touches populated cells, making this O(populated) instead of O(rows × columns).
|
||||
/// </summary>
|
||||
internal void ShiftColumnsLeft(int deletedColumn)
|
||||
{
|
||||
var toRemove = new List<(int row, int column)>();
|
||||
@@ -254,10 +228,6 @@ public class CellStore(Worksheet sheet)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts all cells at or after the specified column right by the given count.
|
||||
/// Only touches populated cells, making this O(populated) instead of O(rows × columns).
|
||||
/// </summary>
|
||||
internal void ShiftColumnsRight(int fromColumn, int count)
|
||||
{
|
||||
var toRekey = new List<((int row, int column) key, Cell cell)>();
|
||||
|
||||
@@ -6,7 +6,6 @@ namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>Reads a CSV stream into a <see cref="Workbook"/> with a single sheet.</summary>
|
||||
static class CsvReader
|
||||
{
|
||||
public static Workbook Read(Stream stream, CsvImportOptions options)
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>Writes a single <see cref="Worksheet"/> to a stream in CSV format.</summary>
|
||||
class CsvWriter(Worksheet sheet, CsvExportOptions options)
|
||||
{
|
||||
public void Write(Stream stream)
|
||||
|
||||
@@ -29,7 +29,6 @@ public class DataCellStore<T>(DataSheet<T> sheet, int pageSize = 20) : CellStore
|
||||
Skip = pageNumber * pageSize
|
||||
});
|
||||
|
||||
// Populate cells for the loaded page
|
||||
for (var i = 0; i < data.Count; i++)
|
||||
{
|
||||
var dataItem = data[i];
|
||||
|
||||
@@ -272,9 +272,6 @@ public class Format
|
||||
Locked is null &&
|
||||
FormulaHidden is null;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the format is changed, allowing for updates to be made to the UI or other components that depend on this format.
|
||||
/// </summary>
|
||||
internal event Action? Changed;
|
||||
|
||||
internal Format Merge(Format format)
|
||||
|
||||
@@ -5,14 +5,8 @@ namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Builds format codes from UI state for the Format Cells dialog.
|
||||
/// </summary>
|
||||
internal static class FormatCodeBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a format code string from the given parameters.
|
||||
/// </summary>
|
||||
public static string Build(NumberFormatCategory category, int decimalPlaces = 2,
|
||||
bool useThousandsSeparator = true, string currencySymbol = "$",
|
||||
string? selectedPreset = null)
|
||||
@@ -46,8 +40,5 @@ internal static class FormatCodeBuilder
|
||||
return $"{pos};{neg};{zero};{text}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available currency symbols.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> CurrencySymbols { get; } = ["$", "\u20AC", "\u00A3", "\u00A5"];
|
||||
}
|
||||
|
||||
@@ -408,7 +408,6 @@ class FormulaEvaluator(Worksheet sheet, Cell currentCell) : IFormulaSyntaxNodeVi
|
||||
return null; // Error already set
|
||||
}
|
||||
|
||||
// Set the argument based on parameter type
|
||||
if (paramDef.Type == ParameterType.Collection)
|
||||
{
|
||||
if (argumentNodes[argumentIndex] is CellSyntaxNode cellNode)
|
||||
@@ -427,7 +426,6 @@ class FormulaEvaluator(Worksheet sheet, Cell currentCell) : IFormulaSyntaxNodeVi
|
||||
}
|
||||
else if (!paramDef.IsRequired)
|
||||
{
|
||||
// Optional parameter not provided - skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
/// <summary>
|
||||
/// Shared implementation for VLOOKUP and HLOOKUP.
|
||||
/// Subclasses provide the axis-specific details via a few one-liner overrides.
|
||||
/// </summary>
|
||||
abstract class LookupFunctionBase : FormulaFunction
|
||||
{
|
||||
public override FunctionParameter[] Parameters =>
|
||||
@@ -18,28 +14,12 @@ abstract class LookupFunctionBase : FormulaFunction
|
||||
new("is_sorted", ParameterType.Single, isRequired: false)
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Returns (searchCount, resultCount) from the range dimensions.
|
||||
/// VLOOKUP searches rows and indexes columns; HLOOKUP is the opposite.
|
||||
/// </summary>
|
||||
protected abstract (int searchCount, int resultCount) GetSearchAndResultCounts(int rows, int columns);
|
||||
|
||||
/// <summary>
|
||||
/// Returns fallback (rows, columns) when the range is not a RangeList.
|
||||
/// VLOOKUP assumes a single-column vector; HLOOKUP assumes a single-row vector.
|
||||
/// </summary>
|
||||
protected abstract (int rows, int columns) GetFallbackDimensions(int count);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the flat index into the range for the search cell at position <paramref name="i"/>
|
||||
/// along the search axis (row 0 for HLOOKUP, column 0 for VLOOKUP).
|
||||
/// </summary>
|
||||
protected abstract int GetSearchCellIndex(int i, int columns);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the flat index into the range for the result cell given the matched search
|
||||
/// position and the 1-based requested index.
|
||||
/// </summary>
|
||||
protected abstract int GetResultCellIndex(int matchPosition, int requestedIndex, int columns);
|
||||
|
||||
public override CellData Evaluate(FunctionArguments arguments)
|
||||
|
||||
@@ -70,7 +70,6 @@ class SubstituteFunction : FormulaFunction
|
||||
count++;
|
||||
if (count == instanceNum)
|
||||
{
|
||||
// Build result: prefix + newText + suffix after oldText
|
||||
var prefix = text.Substring(0, index);
|
||||
var suffix = text.Substring(index + oldText.Length);
|
||||
return CellData.FromString(prefix + newText + suffix);
|
||||
|
||||
@@ -45,22 +45,18 @@ class SumIfFunction : FormulaFunction
|
||||
var rangeCell = range[i];
|
||||
var sumCell = actualSumRange[i];
|
||||
|
||||
// Skip if range cell has an error
|
||||
if (rangeCell.IsError)
|
||||
{
|
||||
return rangeCell;
|
||||
}
|
||||
|
||||
// Skip if sum cell has an error
|
||||
if (sumCell.IsError)
|
||||
{
|
||||
return sumCell;
|
||||
}
|
||||
|
||||
// Check if the range cell matches the criteria
|
||||
if (rangeCell.MatchesCriteria(criteria))
|
||||
{
|
||||
// Add the corresponding sum cell value if it's a number
|
||||
if (sumCell.Type == CellDataType.Number)
|
||||
{
|
||||
sum += sumCell.GetValueOrDefault<double>();
|
||||
@@ -68,7 +64,6 @@ class SumIfFunction : FormulaFunction
|
||||
// If sum cell is empty, treat as 0
|
||||
else if (sumCell.IsEmpty)
|
||||
{
|
||||
// Do nothing, effectively adding 0
|
||||
}
|
||||
// If sum cell is not a number and not empty, skip it
|
||||
}
|
||||
|
||||
@@ -106,11 +106,6 @@ public class MergedCellStore
|
||||
/// </summary>
|
||||
public List<RangeRef> GetOverlappingRanges(RangeRef range) => [.. data.Where(range.Overlaps)];
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts all merged ranges after a row is deleted.
|
||||
/// Ranges fully on the deleted row are removed. Ranges spanning the deleted row shrink.
|
||||
/// Ranges below the deleted row shift up by one.
|
||||
/// </summary>
|
||||
internal void ShiftRowsUp(int deletedRow)
|
||||
{
|
||||
for (int i = data.Count - 1; i >= 0; i--)
|
||||
@@ -147,10 +142,6 @@ public class MergedCellStore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts all merged ranges after rows are inserted.
|
||||
/// Ranges at or below the insert point shift down by count.
|
||||
/// </summary>
|
||||
internal void ShiftRowsDown(int fromRow, int count)
|
||||
{
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
@@ -177,9 +168,6 @@ public class MergedCellStore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts all merged ranges after a column is deleted.
|
||||
/// </summary>
|
||||
internal void ShiftColumnsLeft(int deletedColumn)
|
||||
{
|
||||
for (int i = data.Count - 1; i >= 0; i--)
|
||||
@@ -211,9 +199,6 @@ public class MergedCellStore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts all merged ranges after columns are inserted.
|
||||
/// </summary>
|
||||
internal void ShiftColumnsRight(int fromColumn, int count)
|
||||
{
|
||||
for (int i = 0; i < data.Count; i++)
|
||||
|
||||
@@ -129,9 +129,6 @@ public static class NumberFormat
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a format code represents a date/time format.
|
||||
/// </summary>
|
||||
internal static bool IsDateFormat(string formatCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(formatCode))
|
||||
@@ -375,30 +372,25 @@ public static class NumberFormat
|
||||
var absValue = Math.Abs(value);
|
||||
var isNegative = value < 0;
|
||||
|
||||
// Percentage: multiply by 100
|
||||
if (section.IsPercentage)
|
||||
{
|
||||
absValue *= 100;
|
||||
}
|
||||
|
||||
// Thousands scaling
|
||||
for (var i = 0; i < section.ThousandsScale; i++)
|
||||
{
|
||||
absValue /= 1000;
|
||||
}
|
||||
|
||||
// Count integer and decimal placeholders
|
||||
var intZeros = section.IntegerZeros;
|
||||
var intHashes = section.IntegerHashes;
|
||||
var decimalPlaces = section.DecimalPlaces;
|
||||
var hasThousands = section.HasThousandsSeparator;
|
||||
|
||||
// Round to decimal places
|
||||
absValue = Math.Round(absValue, decimalPlaces, MidpointRounding.AwayFromZero);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Prefix literals
|
||||
sb.Append(section.Prefix);
|
||||
|
||||
// Add negative sign if this is a single-section format and value is negative
|
||||
@@ -413,14 +405,11 @@ public static class NumberFormat
|
||||
// parens are part of the format literals
|
||||
}
|
||||
|
||||
// Split into integer and decimal parts
|
||||
var intPart = (long)Math.Truncate(absValue);
|
||||
var fracPart = absValue - Math.Truncate(absValue);
|
||||
|
||||
// Format integer part
|
||||
var intStr = intPart.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// Pad with zeros
|
||||
var minIntDigits = Math.Max(intZeros, 1);
|
||||
if (intZeros == 0 && intHashes > 0 && intPart == 0)
|
||||
{
|
||||
@@ -433,7 +422,6 @@ public static class NumberFormat
|
||||
intStr = intStr.PadLeft(intZeros, '0');
|
||||
}
|
||||
|
||||
// Add thousands separators
|
||||
if (hasThousands && intStr.Length > 0)
|
||||
{
|
||||
var formatted = new StringBuilder(intStr.Length + (intStr.Length - 1) / 3);
|
||||
@@ -451,7 +439,6 @@ public static class NumberFormat
|
||||
|
||||
sb.Append(intStr);
|
||||
|
||||
// Format decimal part
|
||||
if (decimalPlaces > 0)
|
||||
{
|
||||
sb.Append('.');
|
||||
@@ -511,7 +498,6 @@ public static class NumberFormat
|
||||
}
|
||||
}
|
||||
|
||||
// Suffix (%, etc.)
|
||||
sb.Append(section.Suffix);
|
||||
|
||||
return sb.ToString();
|
||||
@@ -546,7 +532,6 @@ public static class NumberFormat
|
||||
mantissa = absValue / Math.Pow(10, exponent);
|
||||
}
|
||||
|
||||
// Round mantissa to DecimalPlaces
|
||||
mantissa = Math.Round(mantissa, section.DecimalPlaces, MidpointRounding.AwayFromZero);
|
||||
|
||||
// Check if rounding carried over (e.g., 9.995 → 10.00)
|
||||
@@ -563,7 +548,6 @@ public static class NumberFormat
|
||||
}
|
||||
}
|
||||
|
||||
// Format mantissa
|
||||
if (section.DecimalPlaces > 0)
|
||||
{
|
||||
sb.Append(mantissa.ToString("F" + section.DecimalPlaces, CultureInfo.InvariantCulture));
|
||||
@@ -573,11 +557,10 @@ public static class NumberFormat
|
||||
sb.Append(((long)Math.Round(mantissa)).ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// Format exponent part (e.g. "E+00")
|
||||
var expFormat = section.ExponentFormat;
|
||||
var expChar = expFormat[0]; // E or e
|
||||
var expSign = expFormat[1]; // + or -
|
||||
var expDigits = expFormat.Length - 2; // number of 0/# after sign
|
||||
var expChar = expFormat[0];
|
||||
var expSign = expFormat[1];
|
||||
var expDigits = expFormat.Length - 2;
|
||||
|
||||
sb.Append(expChar);
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Parses Excel-compatible number format codes into structured <see cref="FormatSection"/> objects.
|
||||
/// </summary>
|
||||
static class NumberFormatParser
|
||||
{
|
||||
public static ParsedFormat Parse(string formatCode)
|
||||
@@ -250,7 +247,6 @@ static class NumberFormatParser
|
||||
{
|
||||
var c = section[i];
|
||||
|
||||
// Escaped character
|
||||
if (c == '\\' && i + 1 < section.Length)
|
||||
{
|
||||
tokens.Add(new FormatToken(TokenType.Literal, section[i + 1].ToString()));
|
||||
@@ -258,7 +254,6 @@ static class NumberFormatParser
|
||||
continue;
|
||||
}
|
||||
|
||||
// Quoted literal
|
||||
if (c == '"')
|
||||
{
|
||||
var end = section.IndexOf('"', i + 1);
|
||||
@@ -295,7 +290,6 @@ static class NumberFormatParser
|
||||
continue;
|
||||
}
|
||||
|
||||
// AM/PM
|
||||
if (i + 4 < section.Length &&
|
||||
section.Substring(i, 5).Equals("AM/PM", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -352,7 +346,6 @@ static class NumberFormatParser
|
||||
}
|
||||
else
|
||||
{
|
||||
// Look ahead for s
|
||||
for (var j = i + len; j < section.Length; j++)
|
||||
{
|
||||
var nc = section[j];
|
||||
@@ -447,7 +440,7 @@ static class NumberFormatParser
|
||||
if (c is 'E' or 'e' && i + 1 < section.Length && section[i + 1] is '+' or '-')
|
||||
{
|
||||
var start = i;
|
||||
i += 2; // skip E and sign
|
||||
i += 2;
|
||||
while (i < section.Length && section[i] is '0' or '#')
|
||||
{
|
||||
i++;
|
||||
|
||||
@@ -5,34 +5,19 @@ namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Categories of number format codes.
|
||||
/// </summary>
|
||||
internal enum NumberFormatCategory
|
||||
{
|
||||
/// <summary>General format (no specific formatting).</summary>
|
||||
General,
|
||||
/// <summary>Number formats (e.g. 0, 0.00, #,##0).</summary>
|
||||
Number,
|
||||
/// <summary>Currency formats (e.g. $#,##0.00).</summary>
|
||||
Currency,
|
||||
/// <summary>Accounting formats (e.g. _($* #,##0.00_)).</summary>
|
||||
Accounting,
|
||||
/// <summary>Percentage formats (e.g. 0%, 0.00%).</summary>
|
||||
Percentage,
|
||||
/// <summary>Scientific formats (e.g. 0.00E+00).</summary>
|
||||
Scientific,
|
||||
/// <summary>Date formats (e.g. mm/dd/yyyy).</summary>
|
||||
Date,
|
||||
/// <summary>Time formats (e.g. h:mm AM/PM).</summary>
|
||||
Time,
|
||||
/// <summary>Text format.</summary>
|
||||
Text
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides mappings between Excel built-in numFmtId values and format codes.
|
||||
/// </summary>
|
||||
internal static class NumberFormatPresets
|
||||
{
|
||||
private static readonly Dictionary<int, string> IdToCode = new()
|
||||
@@ -74,28 +59,17 @@ internal static class NumberFormatPresets
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the format code for a built-in numFmtId.
|
||||
/// Returns null for unknown IDs.
|
||||
/// </summary>
|
||||
public static string? GetFormatCode(int numFmtId)
|
||||
{
|
||||
return IdToCode.TryGetValue(numFmtId, out var code) ? code : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the numFmtId for a known format code.
|
||||
/// Returns -1 for unknown format codes.
|
||||
/// </summary>
|
||||
public static int GetNumFmtId(string formatCode)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(formatCode);
|
||||
return CodeToId.TryGetValue(formatCode, out var id) ? id : -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the category of a format code.
|
||||
/// </summary>
|
||||
public static NumberFormatCategory GetCategory(string? formatCode)
|
||||
{
|
||||
if (string.IsNullOrEmpty(formatCode) ||
|
||||
@@ -146,9 +120,6 @@ internal static class NumberFormatPresets
|
||||
return NumberFormatCategory.Number;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the preset format codes for a given category.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<(string Name, string FormatCode)> GetPresets(NumberFormatCategory category)
|
||||
{
|
||||
return category switch
|
||||
|
||||
@@ -5,41 +5,18 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a list of cell data originating from a rectangular range, carrying its row/column dimensions.
|
||||
/// </summary>
|
||||
internal class RangeList : List<CellData>
|
||||
{
|
||||
private readonly Worksheet sheet;
|
||||
internal Worksheet Worksheet => sheet;
|
||||
/// <summary>
|
||||
/// Number of rows in the range.
|
||||
/// </summary>
|
||||
public int Rows { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of columns in the range.
|
||||
/// </summary>
|
||||
public int Columns { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting row (absolute) of the range in the sheet.
|
||||
/// </summary>
|
||||
public int StartRow { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting column (absolute) of the range in the sheet.
|
||||
/// </summary>
|
||||
public int StartColumn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RangeList"/> class with specified dimensions.
|
||||
/// </summary>
|
||||
/// <param name="rows">Number of rows in the range.</param>
|
||||
/// <param name="columns">Number of columns in the range.</param>
|
||||
/// <param name="startRow">Starting row index.</param>
|
||||
/// <param name="startColumn">Starting column index.</param>
|
||||
/// <param name="sheet">The sheet instance providing row visibility information.</param>
|
||||
public RangeList(int rows, int columns, int startRow, int startColumn, Worksheet sheet)
|
||||
{
|
||||
Rows = rows;
|
||||
@@ -49,9 +26,6 @@ internal class RangeList : List<CellData>
|
||||
this.sheet = sheet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the row corresponding to the specified item index is hidden.
|
||||
/// </summary>
|
||||
public bool IsRowHiddenAt(int itemIndex)
|
||||
{
|
||||
var rowOffset = itemIndex / Math.Max(1, Columns);
|
||||
|
||||
@@ -30,18 +30,10 @@ public class Selection(Worksheet sheet)
|
||||
/// </summary>
|
||||
public event Action? RangeFinalized;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies subscribers that the user has finished a selection gesture
|
||||
/// (pointer-up after a click or drag).
|
||||
/// </summary>
|
||||
internal void FinalizeRange() => RangeFinalized?.Invoke();
|
||||
|
||||
private bool pendingChange;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the selection to a new cell address and triggers a change event.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
internal void Update(CellRef address)
|
||||
{
|
||||
Cell = address;
|
||||
@@ -49,12 +41,6 @@ public class Selection(Worksheet sheet)
|
||||
TriggerChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-raises the change event after a row/column insert or delete has swapped the cell
|
||||
/// occupying the current selection. The selection coordinates stay the same, but the cell
|
||||
/// beneath them is now a different one, so selection-dependent UI (the formula bar, toolbar
|
||||
/// state) must re-read the active cell. Honours the sheet's batch update like other changes.
|
||||
/// </summary>
|
||||
internal void NotifyContentChanged() => TriggerChange();
|
||||
|
||||
internal void TriggerPendingChange()
|
||||
@@ -79,9 +65,6 @@ public class Selection(Worksheet sheet)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the selection by a specified number of rows and columns.
|
||||
/// </summary>
|
||||
internal CellRef Move(int rowOffset, int columnOffset)
|
||||
{
|
||||
var column = OffsetColumn(columnOffset);
|
||||
@@ -112,9 +95,6 @@ public class Selection(Worksheet sheet)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cycles through the selection within the current range, moving by the specified row and column offsets.
|
||||
/// </summary>
|
||||
internal CellRef Cycle(int rowOffset, int columnOffset)
|
||||
{
|
||||
if (Cell == CellRef.Invalid)
|
||||
@@ -197,9 +177,6 @@ public class Selection(Worksheet sheet)
|
||||
TriggerChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified address is within the active selection range.
|
||||
/// </summary>
|
||||
internal bool IsActive(ColumnRef address) => Range.Contains(address);
|
||||
|
||||
internal bool IsActive(RowRef address) => Range.Contains(address);
|
||||
@@ -298,9 +275,6 @@ public class Selection(Worksheet sheet)
|
||||
TriggerChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extends the current selection by a specified number of rows and columns, adjusting the active cell and range accordingly.
|
||||
/// </summary>
|
||||
internal CellRef Extend(int rowOffset, int columnOffset)
|
||||
{
|
||||
if (Cell == CellRef.Invalid || Range == RangeRef.Invalid)
|
||||
|
||||
@@ -601,17 +601,11 @@ public class TableColumn
|
||||
/// </summary>
|
||||
public class AutoFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoFilter"/> class.
|
||||
/// </summary>
|
||||
internal AutoFilter(Worksheet sheet)
|
||||
{
|
||||
Worksheet = sheet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoFilter"/> class with a range.
|
||||
/// </summary>
|
||||
internal AutoFilter(Worksheet sheet, RangeRef range)
|
||||
{
|
||||
Worksheet = sheet;
|
||||
|
||||
@@ -22,9 +22,6 @@ public partial class Worksheet
|
||||
private readonly HashSet<int> invalidReferenceRows = [];
|
||||
private readonly HashSet<int> invalidReferenceColumns = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the sheet is currently being updated.
|
||||
/// </summary>
|
||||
internal bool IsUpdating { get; private set; }
|
||||
|
||||
private bool isEvaluating;
|
||||
@@ -117,9 +114,6 @@ public partial class Worksheet
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the selected image changes.
|
||||
/// </summary>
|
||||
internal event Action? SelectedImageChanged;
|
||||
|
||||
internal event Action? ImagesChanged;
|
||||
@@ -161,9 +155,6 @@ public partial class Worksheet
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the selected chart changes.
|
||||
/// </summary>
|
||||
internal event Action? SelectedChartChanged;
|
||||
|
||||
internal event Action? ChartsChanged;
|
||||
@@ -194,9 +185,6 @@ public partial class Worksheet
|
||||
|
||||
private readonly HashSet<int> filteredColumns = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of columns that have filters applied.
|
||||
/// </summary>
|
||||
internal IReadOnlySet<int> FilteredColumns => filteredColumns;
|
||||
|
||||
/// <summary>
|
||||
@@ -281,11 +269,6 @@ public partial class Worksheet
|
||||
AutoFilter = new(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the specified cell address to ensure it is within the bounds of the sheet.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <returns></returns>
|
||||
internal CellRef Clamp(CellRef address)
|
||||
{
|
||||
var row = Math.Max(0, Math.Min(RowCount - 1, address.Row));
|
||||
@@ -346,9 +329,6 @@ public partial class Worksheet
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies populated cells in the given range that they should re-validate and/or refresh.
|
||||
/// </summary>
|
||||
internal void RefreshCells(RangeRef range, bool validate = false)
|
||||
{
|
||||
foreach (var cellRef in range.GetCells())
|
||||
@@ -388,13 +368,6 @@ public partial class Worksheet
|
||||
Selection.TriggerPendingChange();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a delimited string representation of the specified range in the sheet.
|
||||
/// </summary>
|
||||
/// <param name="range"></param>
|
||||
/// <param name="rowDelimiter"></param>
|
||||
/// <param name="cellDelimiter"></param>
|
||||
/// <returns></returns>
|
||||
internal string GetDelimitedString(RangeRef range, string rowDelimiter = "\n", string cellDelimiter = "\t")
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rowDelimiter);
|
||||
@@ -430,13 +403,6 @@ public partial class Worksheet
|
||||
return result.ToString().TrimEnd(rowDelimiter.ToCharArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a delimited string into the specified cell address in the sheet, splitting the string into rows and cells based on the specified delimiters.
|
||||
/// </summary>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="cellDelimiter"></param>
|
||||
|
||||
internal void InsertDelimitedString(CellRef address, string value, string cellDelimiter = "\t")
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
@@ -477,16 +443,13 @@ public partial class Worksheet
|
||||
// Shift column metadata (custom widths, hidden state)
|
||||
Columns.ShiftUp(columnIndex);
|
||||
|
||||
// Shift merged cell ranges
|
||||
MergedCells.ShiftColumnsLeft(columnIndex);
|
||||
|
||||
// Mark deleted column index as invalid for references
|
||||
invalidReferenceColumns.Add(columnIndex);
|
||||
|
||||
// Any formulas that reference this column should be turned into =#REF!
|
||||
InvalidateFormulasReferencing(column: columnIndex);
|
||||
|
||||
// Decrease column count
|
||||
ColumnCount--;
|
||||
|
||||
// A different cell now occupies the selection if it sat at or after the deleted
|
||||
@@ -519,16 +482,13 @@ public partial class Worksheet
|
||||
// Shift row metadata (custom heights, hidden state)
|
||||
Rows.ShiftUp(rowIndex);
|
||||
|
||||
// Shift merged cell ranges
|
||||
MergedCells.ShiftRowsUp(rowIndex);
|
||||
|
||||
// Mark deleted row index as invalid for references
|
||||
invalidReferenceRows.Add(rowIndex);
|
||||
|
||||
// Any formulas that reference this row should be turned into =#REF!
|
||||
InvalidateFormulasReferencing(row: rowIndex);
|
||||
|
||||
// Decrease row count
|
||||
RowCount--;
|
||||
|
||||
// A different cell now occupies the selection if it sat at or after the deleted
|
||||
@@ -753,7 +713,6 @@ public partial class Worksheet
|
||||
// Shift row metadata (custom heights, hidden state)
|
||||
Rows.ShiftDown(rowIndex, count);
|
||||
|
||||
// Shift merged cell ranges
|
||||
MergedCells.ShiftRowsDown(rowIndex, count);
|
||||
|
||||
// Adjust formulas: shift row indices at or after the insert point
|
||||
@@ -807,7 +766,6 @@ public partial class Worksheet
|
||||
// Shift column metadata (custom widths, hidden state)
|
||||
Columns.ShiftDown(columnIndex, count);
|
||||
|
||||
// Shift merged cell ranges
|
||||
MergedCells.ShiftColumnsRight(columnIndex, count);
|
||||
|
||||
// Adjust formulas: shift column indices at or after the insert point
|
||||
@@ -845,7 +803,6 @@ public partial class Worksheet
|
||||
public static string Rewrite(string original, FormulaSyntaxTree tree, Func<FormulaToken, CellRef> adjust)
|
||||
{
|
||||
var rewriter = new FormulaRewriter(adjust);
|
||||
// Always start with '=' for formulas
|
||||
rewriter.builder.Append('=');
|
||||
tree.Root.Accept(rewriter);
|
||||
return rewriter.builder.ToString();
|
||||
@@ -904,7 +861,6 @@ public partial class Worksheet
|
||||
|
||||
public override void VisitRange(RangeSyntaxNode rangeSyntaxNode)
|
||||
{
|
||||
// Adjust both sides using the provided delegate
|
||||
var startToken = rangeSyntaxNode.Start.Token;
|
||||
var endToken = rangeSyntaxNode.End.Token;
|
||||
|
||||
|
||||
@@ -9,9 +9,6 @@ namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="Workbook"/> from a stream in the Open XML Spreadsheet format (XLSX).
|
||||
/// </summary>
|
||||
static class XlsxReader
|
||||
{
|
||||
private const double EmuPerPixel = 9525.0;
|
||||
@@ -334,37 +331,27 @@ static class XlsxReader
|
||||
var sheetDoc = XDocument.Load(sheetStream);
|
||||
var sNs = sheetDoc.Root!.Name.Namespace;
|
||||
|
||||
// Parse default row height
|
||||
var defaultRowHeight = ParseDefaultRowHeight(sheetDoc, sNs);
|
||||
|
||||
// Parse frozen panes
|
||||
ParseFrozenPanes(sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse column widths
|
||||
ParseColumnWidths(sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse rows and cells
|
||||
ParseRowsAndCells(sheetDoc, sNs, sheet, styleInfo, sharedStrings, defaultRowHeight);
|
||||
|
||||
// Parse merged cells
|
||||
ParseMergedCells(sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse auto filter
|
||||
ParseAutoFilter(sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse data validations
|
||||
ParseDataValidations(sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse hyperlinks
|
||||
ParseHyperlinks(archive, sheetInfo, sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse drawings (images)
|
||||
ParseDrawings(archive, sheetInfo, sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse tables (must run after rows/cells so column-name fallback can read header values)
|
||||
ParseTables(archive, sheetInfo, sheetDoc, sNs, sheet);
|
||||
|
||||
// Parse sheet protection
|
||||
ParseSheetProtection(sheetDoc, sNs, sheet);
|
||||
|
||||
sheet.Name = sheetInfo.Name;
|
||||
@@ -373,7 +360,6 @@ static class XlsxReader
|
||||
|
||||
private static double ParseDefaultRowHeight(XDocument sheetDoc, XNamespace sNs)
|
||||
{
|
||||
// Parse default row height from sheet format properties
|
||||
var defaultRowHeight = 20.0; // Default fallback
|
||||
var sheetFormatPr = sheetDoc.Descendants(sNs + "sheetFormatPr").FirstOrDefault();
|
||||
if (sheetFormatPr is not null)
|
||||
@@ -622,7 +608,6 @@ static class XlsxReader
|
||||
var range = RangeRef.Parse(refAttribute);
|
||||
sheet.AutoFilter.Range = range;
|
||||
|
||||
// Load filter columns
|
||||
var filterColumns = autoFilterElement.Elements(sNs + "filterColumn").ToList();
|
||||
foreach (var filterColumn in filterColumns)
|
||||
{
|
||||
@@ -730,7 +715,6 @@ static class XlsxReader
|
||||
return;
|
||||
}
|
||||
|
||||
// Load sheet relationships
|
||||
var relMap = new Dictionary<string, string>();
|
||||
var sheetFileName = sheetInfo.FullPath.Split('/').Last();
|
||||
var relsPath = $"xl/worksheets/_rels/{sheetFileName}.rels";
|
||||
@@ -850,7 +834,6 @@ static class XlsxReader
|
||||
}
|
||||
}
|
||||
|
||||
// Parse drawing XML
|
||||
XNamespace xdr = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
|
||||
XNamespace a = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
||||
|
||||
@@ -873,7 +856,6 @@ static class XlsxReader
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse anchor positions
|
||||
var from = ParseCellAnchor(anchor.Element(xdr + "from"), xdr);
|
||||
if (from is null)
|
||||
{
|
||||
@@ -947,7 +929,6 @@ static class XlsxReader
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve image path
|
||||
var imagePath = ResolvePath(drawingDir + "/", imageTarget);
|
||||
var imageEntry = archive.GetEntry(imagePath);
|
||||
if (imageEntry is null)
|
||||
@@ -955,13 +936,11 @@ static class XlsxReader
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read image data
|
||||
using var imageStream = imageEntry.Open();
|
||||
using var ms = new System.IO.MemoryStream();
|
||||
imageStream.CopyTo(ms);
|
||||
image.Data = ms.ToArray();
|
||||
|
||||
// Determine content type from extension
|
||||
var ext2 = imagePath.Split('.').Last().ToLowerInvariant();
|
||||
image.ContentType = ext2 switch
|
||||
{
|
||||
@@ -975,7 +954,6 @@ static class XlsxReader
|
||||
_ => "image/png"
|
||||
};
|
||||
|
||||
// Parse name/description
|
||||
var nvPicPr = pic.Element(xdr + "nvPicPr");
|
||||
var cNvPr = nvPicPr?.Element(xdr + "cNvPr");
|
||||
image.Name = cNvPr?.Attribute("name")?.Value;
|
||||
@@ -1060,14 +1038,12 @@ static class XlsxReader
|
||||
return chart;
|
||||
}
|
||||
|
||||
// Parse title
|
||||
var titleElement = chartElement.Element(c + "title");
|
||||
if (titleElement is not null)
|
||||
{
|
||||
chart.Title = ParseChartTitle(titleElement, c, a);
|
||||
}
|
||||
|
||||
// Parse legend
|
||||
var legendElement = chartElement.Element(c + "legend");
|
||||
if (legendElement is not null)
|
||||
{
|
||||
@@ -1083,7 +1059,6 @@ static class XlsxReader
|
||||
};
|
||||
}
|
||||
|
||||
// Parse plot area
|
||||
var plotArea = chartElement.Element(c + "plotArea");
|
||||
if (plotArea is null)
|
||||
{
|
||||
@@ -1113,7 +1088,6 @@ static class XlsxReader
|
||||
|
||||
chart.ChartType = chartType.Value;
|
||||
|
||||
// Parse series
|
||||
foreach (var ser in groupElement.Elements(c + "ser"))
|
||||
{
|
||||
var series = ParseChartSeries(ser, c, a);
|
||||
@@ -1195,14 +1169,12 @@ static class XlsxReader
|
||||
{
|
||||
var series = new ChartSeries();
|
||||
|
||||
// Parse index
|
||||
var idx = ser.Element(c + "idx")?.Attribute("val")?.Value;
|
||||
if (idx is not null && int.TryParse(idx, out var index))
|
||||
{
|
||||
series.Index = index;
|
||||
}
|
||||
|
||||
// Parse title
|
||||
var tx = ser.Element(c + "tx");
|
||||
if (tx is not null)
|
||||
{
|
||||
@@ -1217,7 +1189,6 @@ static class XlsxReader
|
||||
}
|
||||
}
|
||||
|
||||
// Parse categories
|
||||
var cat = ser.Element(c + "cat");
|
||||
if (cat is not null)
|
||||
{
|
||||
@@ -1240,7 +1211,6 @@ static class XlsxReader
|
||||
}
|
||||
}
|
||||
|
||||
// Parse values
|
||||
var val = ser.Element(c + "val");
|
||||
if (val is not null)
|
||||
{
|
||||
|
||||
@@ -9,9 +9,6 @@ namespace Radzen.Documents.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Writes a <see cref="Workbook"/> to a stream in the Open XML Spreadsheet format (XLSX).
|
||||
/// </summary>
|
||||
class XlsxWriter(Workbook sourceWorkbook)
|
||||
{
|
||||
private const double EmuPerPixel = 9525.0;
|
||||
@@ -258,7 +255,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
mediaPath = $"xl/media/image{globalMediaIndex++}.{ext}";
|
||||
mediaMap[hash] = mediaPath;
|
||||
|
||||
// Write image bytes to archive
|
||||
using (var mediaEntry = archive.CreateEntry(mediaPath).Open())
|
||||
{
|
||||
mediaEntry.Write(image.Data, 0, image.Data.Length);
|
||||
@@ -269,7 +265,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
var relTarget = "../media/" + mediaPath.Split('/').Last();
|
||||
drawingRels.Add((imageRelId, relTarget, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"));
|
||||
|
||||
// Build anchor element
|
||||
XElement anchorElement;
|
||||
var fromElement = new XElement(xdr + "from",
|
||||
new XElement(xdr + "col", image.From.Column.ToString(CultureInfo.InvariantCulture)),
|
||||
@@ -328,7 +323,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
drawingDoc.Root!.Add(anchorElement);
|
||||
}
|
||||
|
||||
// Write chart anchors
|
||||
var chartIndex = 1;
|
||||
foreach (var chart in sheet.Charts)
|
||||
{
|
||||
@@ -338,7 +332,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
|
||||
drawingRels.Add((chartRelId, $"../charts/{chartFileName}", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"));
|
||||
|
||||
// Build anchor element
|
||||
var fromElement = new XElement(xdr + "from",
|
||||
new XElement(xdr + "col", chart.From.Column.ToString(CultureInfo.InvariantCulture)),
|
||||
new XElement(xdr + "colOff", PixelsToEmu(chart.From.ColumnOffset)),
|
||||
@@ -389,19 +382,16 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
|
||||
drawingDoc.Root!.Add(chartAnchor);
|
||||
|
||||
// Save chart XML
|
||||
SaveChartXml(archive, chart, chartPath);
|
||||
|
||||
chartIndex++;
|
||||
}
|
||||
|
||||
// Save drawing XML
|
||||
using (var entry = archive.CreateEntry($"xl/drawings/drawing{drawingIndex}.xml").Open())
|
||||
{
|
||||
drawingDoc.Save(entry);
|
||||
}
|
||||
|
||||
// Save drawing relationships
|
||||
if (drawingRels.Count > 0)
|
||||
{
|
||||
var pkgNs = "http://schemas.openxmlformats.org/package/2006/relationships";
|
||||
@@ -450,7 +440,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
var plotArea = new XElement(c + "plotArea",
|
||||
new XElement(c + "layout"));
|
||||
|
||||
// Create chart type group element
|
||||
var groupElement = CreateChartGroupElement(chart, c);
|
||||
if (groupElement is not null)
|
||||
{
|
||||
@@ -554,7 +543,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
group.Add(new XElement(c + "grouping", new XAttribute("val", grouping)));
|
||||
}
|
||||
|
||||
// Add series
|
||||
foreach (var series in chart.Series)
|
||||
{
|
||||
var ser = new XElement(c + "ser",
|
||||
@@ -901,7 +889,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
var mediaMap = new Dictionary<string, string>();
|
||||
var globalMediaIndex = 1;
|
||||
|
||||
// Process each sheet
|
||||
for (var i = 0; i < sheets.Count; i++)
|
||||
{
|
||||
var sheet = sheets[i];
|
||||
@@ -909,13 +896,11 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
var sheetName = $"sheet{sheetId}.xml";
|
||||
var relId = $"rId{sheetId + 2}";
|
||||
|
||||
// Add sheet relationship (update Target to worksheets subdirectory)
|
||||
workbookRelsElement.Add(new XElement(XName.Get("Relationship", pkgNs),
|
||||
new XAttribute("Id", relId),
|
||||
new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"),
|
||||
new XAttribute("Target", $"worksheets/{sheetName}")));
|
||||
|
||||
// Save individual sheet
|
||||
SaveSheet(archive, sheet, sheetName, sheetId, relId, styleTracker, sharedStrings, sharedStringsDoc, mediaMap, ref globalMediaIndex, ref globalTableIndex);
|
||||
}
|
||||
|
||||
@@ -934,7 +919,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
new XAttribute("Target", "sharedStrings.xml")));
|
||||
}
|
||||
|
||||
// Save workbook relationships
|
||||
using var relsEntry = archive.CreateEntry("xl/_rels/workbook.xml.rels").Open();
|
||||
workbookRels.Save(relsEntry);
|
||||
}
|
||||
@@ -950,25 +934,18 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
var sheetDoc = CreateSheetDocument(sheet, sheetId, relId);
|
||||
var sheetData = sheetDoc.Root!.Element(XName.Get("sheetData", "http://schemas.openxmlformats.org/spreadsheetml/2006/main"))!;
|
||||
|
||||
// Process all rows and cells
|
||||
ProcessSheetData(sheet, sheetData, styleTracker, sharedStrings, sharedStringsDoc);
|
||||
|
||||
// Add merged cells
|
||||
AddMergedCells(sheet, sheetDoc);
|
||||
|
||||
// Add auto filter
|
||||
AddAutoFilter(sheet, sheetDoc);
|
||||
|
||||
// Add data validations
|
||||
AddDataValidations(sheet, sheetDoc);
|
||||
|
||||
// Add sheet protection
|
||||
AddSheetProtection(sheet, sheetDoc);
|
||||
|
||||
// Add hyperlinks
|
||||
var hyperlinkRels = AddHyperlinks(sheet, sheetDoc);
|
||||
|
||||
// Track all sheet relationship entries
|
||||
var sheetRelEntries = new List<(string Id, string Type, string Target, bool External)>();
|
||||
foreach (var (id, url) in hyperlinkRels)
|
||||
{
|
||||
@@ -1337,14 +1314,12 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
var cellElement = new XElement(XName.Get("c", "http://schemas.openxmlformats.org/spreadsheetml/2006/main"),
|
||||
new XAttribute("r", new CellRef(row, col).ToString()));
|
||||
|
||||
// Add style reference if cell has formatting
|
||||
if (HasCellFormatting(cell))
|
||||
{
|
||||
var styleId = GetOrCreateCellStyle(cell, styleTracker);
|
||||
cellElement.Add(new XAttribute("s", styleId));
|
||||
}
|
||||
|
||||
// Add cell value based on type
|
||||
AddCellValue(cellElement, cell, sharedStrings, sharedStringsDoc);
|
||||
|
||||
return cellElement;
|
||||
@@ -1497,7 +1472,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
var newId = 164 + styleTracker.NumberFormats.Count;
|
||||
styleTracker.NumberFormats[formatCode] = newId;
|
||||
|
||||
// Create numFmt element
|
||||
if (styleTracker.NumFmtsElement is null)
|
||||
{
|
||||
styleTracker.NumFmtsElement = new XElement(XName.Get("numFmts", "http://schemas.openxmlformats.org/spreadsheetml/2006/main"),
|
||||
@@ -1565,9 +1539,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
styleTracker.FillsElement.Attribute("count")!.Value = (styleTracker.FillStyles.Count + 2).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cell style element in the styles document.
|
||||
/// </summary>
|
||||
private void CreateCellStyleElement(Cell cell, int fontId, int fillId, int borderId, int numFmtId, StyleTracker styleTracker)
|
||||
{
|
||||
var xfElement = new XElement(XName.Get("xf", "http://schemas.openxmlformats.org/spreadsheetml/2006/main"),
|
||||
@@ -1615,9 +1586,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
styleTracker.CellXfsElement.Attribute("count")!.Value = (styleTracker.CellStyles.Count + 1).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an alignment element for cell formatting.
|
||||
/// </summary>
|
||||
private static XElement CreateAlignmentElement(Cell cell)
|
||||
{
|
||||
var alignmentElement = new XElement(XName.Get("alignment", "http://schemas.openxmlformats.org/spreadsheetml/2006/main"));
|
||||
@@ -1783,7 +1751,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
new XAttribute("ref", sheet.AutoFilter.Range.Value.ToString()),
|
||||
new XAttribute(XName.Get("uid", "http://schemas.microsoft.com/office/spreadsheetml/2014/revision"), uid));
|
||||
|
||||
// Process each filter and create filterColumn elements
|
||||
foreach (var filter in sheet.Filters)
|
||||
{
|
||||
var filterColumn = CreateFilterColumn(filter, sheet.AutoFilter.Range.Value);
|
||||
@@ -2185,7 +2152,6 @@ class XlsxWriter(Workbook sourceWorkbook)
|
||||
|
||||
var sheetsElement = workbook.Root!.Element(mainNs + "sheets")!;
|
||||
|
||||
// Add sheet references
|
||||
for (var i = 0; i < sheets.Count; i++)
|
||||
{
|
||||
var sheet = sheets[i];
|
||||
|
||||
@@ -5,9 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
namespace Radzen.Blazor.Spreadsheet;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the direction of an autofill operation.
|
||||
/// </summary>
|
||||
enum AutofillDirection
|
||||
{
|
||||
Down,
|
||||
@@ -16,22 +13,12 @@ enum AutofillDirection
|
||||
Left
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command that fills a target range by repeating a source range pattern.
|
||||
/// Supports numeric series, date series, formula adjustment, and plain copy.
|
||||
/// </summary>
|
||||
class AutofillCommand : ICommand, IProtectedCommand
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public SheetAction RequiredAction => SheetAction.EditCell;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SpreadsheetFeature? Feature => SpreadsheetFeature.Autofill;
|
||||
|
||||
/// <summary>
|
||||
/// Computes the autofill target range from the source selection and the cell the user dragged to.
|
||||
/// Constrains to the dominant axis (vertical or horizontal).
|
||||
/// </summary>
|
||||
internal static RangeRef ComputeRange(RangeRef source, CellRef target)
|
||||
{
|
||||
var rowDelta = 0;
|
||||
@@ -83,9 +70,6 @@ class AutofillCommand : ICommand, IProtectedCommand
|
||||
return RangeRef.Invalid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the fill direction by comparing the fill range to the source range.
|
||||
/// </summary>
|
||||
internal static AutofillDirection GetDirection(RangeRef source, RangeRef fill)
|
||||
{
|
||||
if (fill.End.Row > source.End.Row)
|
||||
@@ -376,10 +360,6 @@ class AutofillCommand : ICommand, IProtectedCommand
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to detect an arithmetic numeric or date series in the source values.
|
||||
/// Returns null if no series pattern is detected.
|
||||
/// </summary>
|
||||
private static SeriesInfo? DetectSeries(List<object?> sourceValues)
|
||||
{
|
||||
if (sourceValues.Count < 2)
|
||||
@@ -464,9 +444,6 @@ class AutofillCommand : ICommand, IProtectedCommand
|
||||
|
||||
private abstract class SeriesInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the nth fill value. n is 1-based (1 = first cell past the source boundary).
|
||||
/// </summary>
|
||||
public abstract object GetValue(int n, AutofillDirection direction);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,11 +86,9 @@ public partial class CellMenu : ComponentBase
|
||||
|
||||
await base.SetParametersAsync(parameters);
|
||||
|
||||
// Check if any of the key parameters have changed
|
||||
if (didRowChange || didColumnChange || didSheetChange)
|
||||
{
|
||||
InvalidateCache();
|
||||
// Reinitialize the selected filter values
|
||||
InitializeSelectedFilterValues();
|
||||
}
|
||||
}
|
||||
@@ -209,8 +207,7 @@ public partial class CellMenu : ComponentBase
|
||||
|
||||
var totalItems = availableValues.Count + (showBlank ? 1 : 0);
|
||||
var selectedCount = availableValues.Count(v => selectedFilterValues.Contains(v.Value));
|
||||
|
||||
// Add 1 to selected count if blank is selected
|
||||
|
||||
if (showBlank && selectedFilterValues.Contains(null))
|
||||
{
|
||||
selectedCount++;
|
||||
@@ -314,8 +311,7 @@ public partial class CellMenu : ComponentBase
|
||||
var showBlank = ShouldShowBlankOption();
|
||||
var totalItems = availableValues.Count + (showBlank ? 1 : 0);
|
||||
var selectedCount = availableValues.Count(v => selectedFilterValues.Contains(v.Value));
|
||||
|
||||
// Add 1 to selected count if blank is selected
|
||||
|
||||
if (showBlank && selectedFilterValues.Contains(null))
|
||||
{
|
||||
selectedCount++;
|
||||
@@ -336,7 +332,6 @@ public partial class CellMenu : ComponentBase
|
||||
|
||||
if (rangeToUse != RangeRef.Invalid)
|
||||
{
|
||||
// Create a new range for the current column using the determined range
|
||||
var columnRange = new RangeRef(
|
||||
new CellRef(rangeToUse.Start.Row, Column),
|
||||
new CellRef(rangeToUse.End.Row, Column)
|
||||
|
||||
@@ -223,7 +223,6 @@ public partial class CellView : CellBase, IDisposable
|
||||
|
||||
private bool ShouldShowMenuForMergedCell()
|
||||
{
|
||||
// Check if the current cell is part of a merged range
|
||||
var mergedRange = Worksheet.MergedCells.GetMergedRange(new CellRef(Row, Column));
|
||||
|
||||
if (mergedRange == RangeRef.Invalid)
|
||||
|
||||
@@ -389,7 +389,6 @@ public partial class ChartOverlay : ComponentBase, IDisposable
|
||||
chartBuilder.CloseComponent();
|
||||
}
|
||||
|
||||
// Add axes for non-pie/donut charts
|
||||
if (chart.ChartType != SpreadsheetChartType.Pie && chart.ChartType != SpreadsheetChartType.Donut)
|
||||
{
|
||||
chartBuilder.OpenComponent<RadzenCategoryAxis>(10000);
|
||||
@@ -399,7 +398,6 @@ public partial class ChartOverlay : ComponentBase, IDisposable
|
||||
chartBuilder.CloseComponent();
|
||||
}
|
||||
|
||||
// Add legend if configured
|
||||
if (chart.ShowLegend)
|
||||
{
|
||||
chartBuilder.OpenComponent<RadzenLegend>(10002);
|
||||
|
||||
@@ -6,10 +6,8 @@ namespace Radzen.Blazor.Spreadsheet;
|
||||
|
||||
class ClearContentsCommand(Worksheet sheet, RangeRef range) : ICommand, IProtectedCommand
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public SheetAction RequiredAction => SheetAction.EditCell;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SpreadsheetFeature? Feature => SpreadsheetFeature.Editing;
|
||||
|
||||
private readonly Dictionary<CellRef, (object? value, string? formula)> snapshot = [];
|
||||
|
||||
@@ -119,7 +119,6 @@ public abstract class HeaderBase : CellBase, IDisposable
|
||||
return dirty;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (Worksheet is not null)
|
||||
|
||||
@@ -84,7 +84,6 @@ public abstract class SheetSnapshotCommandBase : ICommand
|
||||
}
|
||||
}
|
||||
|
||||
// Restore backed-up cells
|
||||
foreach (var ((row, column), (value, formula, format)) in backupCells)
|
||||
{
|
||||
var cell = sheet.Cells[row, column];
|
||||
|
||||
@@ -117,7 +117,6 @@ public class SheetView
|
||||
{
|
||||
if (axis.Frozen > 0)
|
||||
{
|
||||
// Calculate position after frozen items
|
||||
for (int index = 0; index < axis.Frozen; index++)
|
||||
{
|
||||
if (!axis.IsHidden(index))
|
||||
|
||||
@@ -5,17 +5,8 @@ namespace Radzen.Blazor.Spreadsheet;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Visual presets for the built-in Excel table styles. Each preset captures the colors
|
||||
/// and emphasis used by <see cref="Documents.Spreadsheet.Table.TableStyle"/> when rendering
|
||||
/// inside <see cref="RadzenSpreadsheet"/>. Presets are generated for the full Excel
|
||||
/// palette (Light1..21, Medium1..28, Dark1..11) so every named style produces a
|
||||
/// distinct visual output. Colors approximate the Office default theme; precise
|
||||
/// Excel theming would require reading <c>theme1.xml</c> from the package.
|
||||
/// </summary>
|
||||
internal static class TableStylePresets
|
||||
{
|
||||
/// <summary>Color set for one named table style.</summary>
|
||||
internal sealed record Preset(
|
||||
string? HeaderBackground,
|
||||
string? HeaderColor,
|
||||
@@ -62,7 +53,6 @@ internal static class TableStylePresets
|
||||
|
||||
private static readonly Dictionary<string, Preset> presets = BuildAll();
|
||||
|
||||
/// <summary>Looks up a preset by name; falls back to a sensible default.</summary>
|
||||
internal static Preset Get(string? name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
|
||||
@@ -20,14 +20,12 @@
|
||||
{
|
||||
if (Worksheet is null) return false;
|
||||
|
||||
// Check if we're in a data table
|
||||
var dataTable = GetCurrentTable();
|
||||
if (dataTable is not null)
|
||||
{
|
||||
return dataTable.ShowFilterButton;
|
||||
}
|
||||
|
||||
// Check if sheet has auto filter
|
||||
return Worksheet.AutoFilter.Range is not null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@ public abstract class FormatToggleToolBase : ComponentBase, IDisposable
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (Worksheet?.Selection is not null)
|
||||
|
||||
@@ -59,7 +59,6 @@ public abstract class SpreadsheetToolBase : ComponentBase, IDisposable
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (Worksheet?.Selection is not null)
|
||||
|
||||
@@ -562,7 +562,6 @@ public partial class VirtualGrid : ComponentBase, IAsyncDisposable, IVirtualGrid
|
||||
|
||||
foreach (var range in visibleMergedCells)
|
||||
{
|
||||
// Adjust the range to start from the first visible row and column
|
||||
var adjustedRange = AdjustRangeForHiddenRowsAndColumns(range);
|
||||
|
||||
// Skip if the entire range is hidden
|
||||
|
||||
Reference in New Issue
Block a user