Skip to content

Commit

Permalink
Feature/issue946 (#1082)
Browse files Browse the repository at this point in the history
* WIP on LoadFromCollection hyperlinks

* Fixed an issue with auto filters

* Fixed failing test

* #1058, #946 added hyperlink style to hyperlinks created by the LoadFromCollection method

---------

Co-authored-by: JanKallman <[email protected]>
Co-authored-by: swmal <{ID}+username}@users.noreply.github.com>
  • Loading branch information
3 people committed Oct 2, 2023
1 parent 9baac84 commit 5af9d33
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 27 deletions.
9 changes: 9 additions & 0 deletions src/EPPlus/Attributes/EpplusTableColumnAttributeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ public bool Hidden
set;
}

/// <summary>
/// Indicates whether the Built in (default) hyperlink style should be
/// applied to hyperlinks or not. Default value is true.
/// </summary>
public bool UseBuiltInHyperlinkStyle
{
get; set;
} = true;

/// <summary>
/// If not <see cref="RowFunctions.None"/> the last cell in the column (the totals row) will contain a formula of the specified type.
/// </summary>
Expand Down
29 changes: 18 additions & 11 deletions src/EPPlus/ExcelWorksheet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,13 +495,7 @@ public ExcelAutoFilter AutoFilter
if (_autoFilter == null)
{
CheckSheetTypeAndNotDisposed();
if(GetXmlNodeString($"{AutoFilterPath}/@ref") == "")
{
SetXmlNodeString($"{AutoFilterPath}/@ref", "");
}

var node = _worksheetXml.SelectSingleNode($"//{AutoFilterPath}", NameSpaceManager);
_autoFilter = new ExcelAutoFilter(NameSpaceManager, node, this);
_autoFilter = new ExcelAutoFilter(NameSpaceManager, TopNode, this);
}
return _autoFilter;
}
Expand Down Expand Up @@ -3467,12 +3461,13 @@ internal void SetValueStyleIdInner(int row, int col, object value, int styleId)
/// <param name="toRow">end row</param>
/// <param name="toColumn">end column</param>
/// <param name="values">set values</param>
/// <param name="addHyperlinkStyles">Will add built in styles for hyperlinks</param>
/// <param name="setHyperLinkFromValue">If the value is of type Uri or ExcelHyperlink the Hyperlink property is set.</param>
internal void SetRangeValueInner(int fromRow, int fromColumn, int toRow, int toColumn, object[,] values, bool setHyperLinkFromValue)
internal void SetRangeValueInner(int fromRow, int fromColumn, int toRow, int toColumn, object[,] values, bool setHyperLinkFromValue, bool addHyperlinkStyles = false)
{
if (setHyperLinkFromValue)
{
SetValuesWithHyperLink(fromRow, fromColumn, values);
SetValuesWithHyperLink(fromRow, fromColumn, values, addHyperlinkStyles);
}
else
{
Expand All @@ -3484,11 +3479,11 @@ internal void SetRangeValueInner(int fromRow, int fromColumn, int toRow, int toC
_metadataStore.Clear(fromRow, fromColumn, values.GetUpperBound(0) + 1, values.GetUpperBound(1) + 1);
}

private void SetValuesWithHyperLink(int fromRow, int fromColumn, object[,] values)
private void SetValuesWithHyperLink(int fromRow, int fromColumn, object[,] values, bool addHyperlinkStyles)
{
var rowBound = values.GetUpperBound(0);
var colBound = values.GetUpperBound(1);

var hyperlinkStylesAdded = false;
for (int r = 0; r <= rowBound; r++)
{
for (int c = 0; c <= colBound; c++)
Expand All @@ -3504,6 +3499,18 @@ private void SetValuesWithHyperLink(int fromRow, int fromColumn, object[,] value
var t = v.GetType();
if (t == typeof(Uri) || t == typeof(ExcelHyperLink))
{
if (!hyperlinkStylesAdded && addHyperlinkStyles)
{
if (!Workbook.Styles.NamedStyles.ExistsKey("Hyperlink"))
{
var hls = Workbook.Styles.CreateNamedStyle("Hyperlink");
hls.BuildInId = 8;
hls.Style.Font.UnderLine = true;
hls.Style.Font.Color.SetColor(System.Drawing.Color.FromArgb(0x0563C1));
}
hyperlinkStylesAdded = true;
}
Cells[row, col].StyleName = "Hyperlink";
_hyperLinks.SetValue(row, col, (Uri)v);
if (v is ExcelHyperLink hl)
{
Expand Down
25 changes: 18 additions & 7 deletions src/EPPlus/Filter/ExcelAutoFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ internal ExcelAutoFilter(XmlNamespaceManager namespaceManager, XmlNode topNode,
{
_columns = new ExcelFilterColumnCollection(namespaceManager, topNode, this);
_worksheet = worksheet;
if (GetXmlNodeString("@ref") != "")
if (GetXmlNodeString("d:autoFilter/@ref") != "")
{
Address = new ExcelAddressBase(GetXmlNodeString("@ref"));
Address = new ExcelAddressBase(GetXmlNodeString("d:autoFilter/@ref"));
}
}
internal ExcelAutoFilter(XmlNamespaceManager namespaceManager, XmlNode topNode, ExcelTable table) : base(namespaceManager, topNode)
{
_columns = new ExcelFilterColumnCollection(namespaceManager, topNode, this);
_worksheet = table.WorkSheet;
_table = table;
if(GetXmlNodeString("@ref") != "")
if(GetXmlNodeString("d:autoFilter/@ref") != "")
{
Address = new ExcelAddressBase(GetXmlNodeString("@ref"));
Address = new ExcelAddressBase(GetXmlNodeString("d:autoFilter/@ref"));
}
}

Expand Down Expand Up @@ -98,7 +98,7 @@ public ExcelAddressBase Address
_worksheet.CheckSheetTypeAndNotDisposed();
if(_address == null)
{
string address = GetXmlNodeString("@ref");
string address = GetXmlNodeString("d:autoFilter/@ref");
if (address == "")
{
_address = null;
Expand All @@ -120,7 +120,7 @@ internal set

if (value == null)
{
DeleteAllNode($"@ref");
DeleteAllNode($"d:autoFilter/@ref");
_columns = null;
}
else
Expand All @@ -130,7 +130,7 @@ internal set
_columns = new ExcelFilterColumnCollection(NameSpaceManager, TopNode, this);
}

SetXmlNodeString($"@ref", value.Address);
SetXmlNodeString($"d:autoFilter/@ref", value.Address);
}

_address = value;
Expand Down Expand Up @@ -159,5 +159,16 @@ public void ClearAll()
_table = null;
Address = null;
}
internal XmlNode CreateAutoFilterTopNode()
{
if(_table==null)
{
return _worksheet.CreateNode("d:autoFilter");
}
else
{
return _table.CreateNode("d:autoFilter");
}
}
}
}
25 changes: 20 additions & 5 deletions src/EPPlus/Filter/ExcelFilterColumnCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
*************************************************************************************************
01/27/2020 EPPlus Software AB Initial release EPPlus 5
*************************************************************************************************/
using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml;

namespace OfficeOpenXml.Filter
Expand All @@ -27,7 +29,7 @@ public class ExcelFilterColumnCollection : XmlHelper, IEnumerable<ExcelFilterCol
internal ExcelFilterColumnCollection(XmlNamespaceManager namespaceManager, XmlNode topNode, ExcelAutoFilter autofilter) : base(namespaceManager, topNode)
{
_autoFilter = autofilter;
foreach (XmlElement node in topNode.SelectNodes("d:filterColumn", namespaceManager))
foreach (XmlElement node in topNode.SelectNodes("d:autoFilter/d:filterColumn", namespaceManager))
{
if(!int.TryParse(node.Attributes["colId"].Value, out int position))
{
Expand Down Expand Up @@ -69,13 +71,17 @@ public int Count
internal XmlNode Add(int position, string topNodeName)
{
XmlElement node;
if (position >= _autoFilter.Address.Columns)
if(_autoFilter.Address==null)
{
throw (new ArgumentOutOfRangeException("Position is outside of the range"));
throw (new InvalidOperationException("Cannot add a column to the auto filter until an address is set."));
}
else if (position >= _autoFilter.Address.Columns)
{
throw (new ArgumentException($"Position {position} is outside of the range if the filter column collection"));
}
if (_columns.ContainsKey(position))
{
throw (new ArgumentOutOfRangeException("Position already exists"));
throw (new ArgumentException($"Filter column at position {position} already exists"));
}
foreach (var c in _columns.Values)
{
Expand All @@ -91,6 +97,11 @@ internal XmlNode Add(int position, string topNodeName)

private XmlElement GetColumnNode(int position, string topNodeName)
{
if (TopNode.LocalName != "autoFilter")
{
TopNode = _autoFilter.CreateAutoFilterTopNode();
}

XmlElement node = TopNode.OwnerDocument.CreateElement("filterColumn", ExcelPackage.schemaMain);
node.SetAttribute("colId", position.ToString());
var subNode = TopNode.OwnerDocument.CreateElement(topNodeName, ExcelPackage.schemaMain);
Expand Down Expand Up @@ -233,7 +244,11 @@ public void Remove(ExcelFilterColumn column)
/// </summary>
public void Clear()
{
_columns.Clear();
while(_columns.Count > 0 )
{
var c = _columns.Values.First();
Remove(c);
}
}

}
Expand Down
2 changes: 2 additions & 0 deletions src/EPPlus/LoadFunctions/ColumnInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public ColumnInfo()

public string NumberFormat { get; set; }

public bool UseBuiltInHyperlinkStyle { get; set; }

public RowFunctions TotalsRowFunction { get; set; }

public string TotalsRowFormula { get; set; }
Expand Down
7 changes: 5 additions & 2 deletions src/EPPlus/LoadFunctions/LoadFunctionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ public LoadFunctionBase(ExcelRangeBase range, LoadFunctionFunctionParamsBase par
PrintHeaders = parameters.PrintHeaders;
TableStyle = parameters.TableStyle;
TableName = parameters.TableName?.Trim();
_useBuiltInStylesForHyperlinks = parameters.UseBuiltInStylesForHyperlinks;
}

private readonly bool _useBuiltInStylesForHyperlinks;

/// <summary>
/// The range to which the data should be loaded
/// </summary>
Expand Down Expand Up @@ -95,7 +98,7 @@ internal ExcelRangeBase Load()
}
else
{
ws.SetRangeValueInner(Range._fromRow, Range._fromCol, Range._fromRow + nRows - 1, Range._fromCol + nCols - 1, values, true);
ws.SetRangeValueInner(Range._fromRow, Range._fromCol, Range._fromRow + nRows - 1, Range._fromCol + nCols - 1, values, true, _useBuiltInStylesForHyperlinks);
}


Expand Down Expand Up @@ -168,7 +171,7 @@ private void SetValuesAndFormulas(int nRows, int nCols, object[,] values, Dictio
var fromRow = Range._fromRow;
var rangeCol = Range._fromCol + col;
var toRow = Range._fromRow + nRows - 1;
ws.SetRangeValueInner(fromRow, rangeCol, toRow, rangeCol, columnValues, true);
ws.SetRangeValueInner(fromRow, rangeCol, toRow, rangeCol, columnValues, true, _useBuiltInStylesForHyperlinks);
}

}
Expand Down
10 changes: 10 additions & 0 deletions src/EPPlus/LoadFunctions/Params/LoadFunctionFunctionParamsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,15 @@ public string TableName
{
get; set;
} = null;

/// <summary>
/// If true, EPPlus will add the built in (default) styles for hyperlinks and apply them on any member
/// that is of the <see cref="Uri"/> or <see cref="ExcelHyperLink"/> types. Default value is true.
/// </summary>
public bool UseBuiltInStylesForHyperlinks
{
get;
set;
} = true;
}
}
4 changes: 2 additions & 2 deletions src/EPPlus/Table/ExcelTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -734,8 +734,8 @@ private void SetAutoFilter()
{
if (_autoFilter == null)
{
var node = TopNode.SelectSingleNode(AUTOFILTER_PATH, NameSpaceManager);
_autoFilter = new ExcelAutoFilter(NameSpaceManager, node, this);
//var node = TopNode.SelectSingleNode(AUTOFILTER_PATH, NameSpaceManager);
_autoFilter = new ExcelAutoFilter(NameSpaceManager, TopNode, this);
_autoFilter.Address = AutoFilterAddress;
}

Expand Down
37 changes: 37 additions & 0 deletions src/EPPlusTest/LoadFunctions/LoadFromCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,30 @@ namespace EPPlusTest.LoadFunctions
[TestClass]
public class LoadFromCollectionTests : TestBase
{
[EpplusTable(AutofitColumns = true, PrintHeaders = true, TableStyle = TableStyles.Light10)]
internal class Company
{
public Company(int id, string name, Uri url)
{
Id = id;
Name = name;
Url = url;
}

[EpplusTableColumn(Header = "Id", Order = 1)]
public int Id
{
get; set;
}

[EpplusTableColumn(Header = "Name", Order = 2)]
public string Name { get; set; }

[EpplusTableColumn(Header = "Homepage", Order = 3)]
public Uri Url { get; set; }

}

internal abstract class BaseClass
{
public string Id { get; set; }
Expand Down Expand Up @@ -527,6 +551,19 @@ public void LoadListOfClassWithEnumWithDescription()
SaveAndCleanup(package);
}
}
[TestMethod]
public void LoadWithAttributesTest()
{
var l = new List<Company>();
l.Add(new Company(1, "EPPlus Software AB", new Uri("https://epplussoftware.com")));

using (var package = OpenPackage("LoadFromCollectionAttr.xlsx", true))
{
var sheet = package.Workbook.Worksheets.Add("test");
sheet.Cells["A1"].LoadFromCollection(l, x => x.UseBuiltInStylesForHyperlinks = true);

SaveAndCleanup(package);
}
}
}
}

0 comments on commit 5af9d33

Please sign in to comment.