diff --git a/.gitattributes b/.gitattributes
index 505c036..68cc179 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,7 +4,7 @@
# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
-*.cs text diff=csharp eol=lf
+*.cs text diff=csharp eol=crlf
# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf
diff --git a/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML.csproj
index 3a8443f..ef9ef98 100644
--- a/ClosedXML/ClosedXML.csproj
+++ b/ClosedXML/ClosedXML.csproj
@@ -72,11 +72,13 @@
+
+
diff --git a/ClosedXML/Excel/Drawings/IXLPicture.cs b/ClosedXML/Excel/Drawings/IXLPicture.cs
index d059776..2c2f750 100644
--- a/ClosedXML/Excel/Drawings/IXLPicture.cs
+++ b/ClosedXML/Excel/Drawings/IXLPicture.cs
@@ -15,17 +15,32 @@
XLPictureFormat Format { get; }
Int32 Height { get; set; }
+
MemoryStream ImageStream { get; }
+
Int32 Left { get; set; }
+
String Name { get; set; }
+
Int32 OriginalHeight { get; }
+
Int32 OriginalWidth { get; }
+
XLPicturePlacement Placement { get; set; }
+
Int32 Top { get; set; }
+
IXLAddress TopLeftCellAddress { get; }
+
Int32 Width { get; set; }
+
IXLWorksheet Worksheet { get; }
+ ///
+ /// Deletes this picture.
+ ///
+ void Delete();
+
Point GetOffset(XLMarkerPosition position);
IXLPicture MoveTo(Int32 left, Int32 top);
diff --git a/ClosedXML/Excel/Drawings/IXLPictures.cs b/ClosedXML/Excel/Drawings/IXLPictures.cs
new file mode 100644
index 0000000..ff449cd
--- /dev/null
+++ b/ClosedXML/Excel/Drawings/IXLPictures.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+
+namespace ClosedXML.Excel.Drawings
+{
+ public interface IXLPictures : IEnumerable
+ {
+ int Count { get; }
+
+ IXLPicture Add(Stream stream);
+
+ IXLPicture Add(Stream stream, String name);
+
+ IXLPicture Add(Stream stream, XLPictureFormat format);
+
+ IXLPicture Add(Stream stream, XLPictureFormat format, String name);
+
+ IXLPicture Add(Bitmap bitmap);
+
+ IXLPicture Add(Bitmap bitmap, String name);
+
+ IXLPicture Add(String imageFile);
+
+ IXLPicture Add(String imageFile, String name);
+
+ void Delete(String pictureName);
+
+ void Delete(IXLPicture picture);
+
+ IXLPicture Picture(String pictureName);
+
+ bool TryGetPicture(string pictureName, out IXLPicture picture);
+ }
+}
diff --git a/ClosedXML/Excel/Drawings/XLPicture.cs b/ClosedXML/Excel/Drawings/XLPicture.cs
index c87d54c..86d45ae 100644
--- a/ClosedXML/Excel/Drawings/XLPicture.cs
+++ b/ClosedXML/Excel/Drawings/XLPicture.cs
@@ -12,9 +12,11 @@
[DebuggerDisplay("{Name}")]
internal class XLPicture : IXLPicture
{
+ private const String InvalidNameChars = @":\/?*[]";
private static IDictionary FormatMap;
private readonly IXLWorksheet _worksheet;
private Int32 height;
+ private String name = string.Empty;
private Int32 width;
static XLPicture()
@@ -147,7 +149,28 @@
}
}
- public String Name { get; set; }
+ public String Name
+ {
+ get { return name; }
+ set
+ {
+ if (name == value) return;
+
+ if (value.IndexOfAny(InvalidNameChars.ToCharArray()) != -1)
+ throw new ArgumentException($"Picture names cannot contain any of the following characters: {InvalidNameChars}");
+
+ if (XLHelper.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("Picture names cannot be empty");
+
+ if (value.Length > 31)
+ throw new ArgumentException("Picture names cannot be more than 31 characters");
+
+ if ((_worksheet.Pictures.FirstOrDefault(p => p.Name.Equals(value, StringComparison.OrdinalIgnoreCase)) ?? this) != this)
+ throw new ArgumentException($"The picture name '{value}' already exists.");
+
+ name = value;
+ }
+ }
public Int32 OriginalHeight { get; private set; }
@@ -203,6 +226,11 @@
internal String RelId { get; set; }
+ public void Delete()
+ {
+ Worksheet.Pictures.Delete(this.Name);
+ }
+
public void Dispose()
{
this.ImageStream.Dispose();
@@ -326,4 +354,4 @@
this.height = bitmap.Height;
}
}
-}
\ No newline at end of file
+}
diff --git a/ClosedXML/Excel/Drawings/XLPictures.cs b/ClosedXML/Excel/Drawings/XLPictures.cs
new file mode 100644
index 0000000..3381309
--- /dev/null
+++ b/ClosedXML/Excel/Drawings/XLPictures.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+
+namespace ClosedXML.Excel.Drawings
+{
+ internal class XLPictures : IXLPictures, IEnumerable
+ {
+ private readonly List _pictures = new List();
+ private readonly XLWorksheet _worksheet;
+
+ public XLPictures(XLWorksheet worksheet)
+ {
+ _worksheet = worksheet;
+ }
+
+ public int Count
+ {
+ [DebuggerStepThrough]
+ get { return _pictures.Count; }
+ }
+
+ public IXLPicture Add(Stream stream)
+ {
+ var picture = new XLPicture(_worksheet, stream);
+ _pictures.Add(picture);
+ picture.Name = GetNextPictureName();
+ return picture;
+ }
+
+ public IXLPicture Add(Stream stream, string name)
+ {
+ var picture = Add(stream);
+ picture.Name = name;
+ return picture;
+ }
+
+ public Drawings.IXLPicture Add(Stream stream, XLPictureFormat format)
+ {
+ var picture = new XLPicture(_worksheet, stream, format);
+ _pictures.Add(picture);
+ picture.Name = GetNextPictureName();
+ return picture;
+ }
+
+ public IXLPicture Add(Stream stream, XLPictureFormat format, string name)
+ {
+ var picture = Add(stream, format);
+ picture.Name = name;
+ return picture;
+ }
+
+ public IXLPicture Add(Bitmap bitmap)
+ {
+ var picture = new XLPicture(_worksheet, bitmap);
+ _pictures.Add(picture);
+ picture.Name = GetNextPictureName();
+ return picture;
+ }
+
+ public IXLPicture Add(Bitmap bitmap, string name)
+ {
+ var picture = Add(bitmap);
+ picture.Name = name;
+ return picture;
+ }
+
+ public IXLPicture Add(string imageFile)
+ {
+ using (var bitmap = Image.FromFile(imageFile) as Bitmap)
+ {
+ var picture = new XLPicture(_worksheet, bitmap);
+ _pictures.Add(picture);
+ picture.Name = GetNextPictureName();
+ return picture;
+ }
+ }
+
+ public IXLPicture Add(string imageFile, string name)
+ {
+ var picture = Add(imageFile);
+ picture.Name = name;
+ return picture;
+ }
+
+ public void Delete(IXLPicture picture)
+ {
+ Delete(picture.Name);
+ }
+
+ public void Delete(string pictureName)
+ {
+ _pictures.RemoveAll(picture => picture.Name.Equals(pictureName, StringComparison.OrdinalIgnoreCase));
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _pictures.Cast().GetEnumerator();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)_pictures).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IXLPicture Picture(string pictureName)
+ {
+ IXLPicture p;
+
+ if (TryGetPicture(pictureName, out p))
+ return p;
+
+ throw new ArgumentException($"There isn't a picture named '{pictureName}'.");
+ }
+
+ public bool TryGetPicture(string pictureName, out IXLPicture picture)
+ {
+ var matches = _pictures.Where(p => p.Name.Equals(pictureName, StringComparison.OrdinalIgnoreCase));
+ if (matches.Any())
+ {
+ picture = matches.Single();
+ return true;
+ }
+ picture = null;
+ return false;
+ }
+
+ private String GetNextPictureName()
+ {
+ var pictureNumber = this.Count;
+ while (_pictures.Any(p => p.Name == $"Picture {pictureNumber}"))
+ {
+ pictureNumber++;
+ }
+ return $"Picture {pictureNumber}";
+ }
+ }
+}
diff --git a/ClosedXML/Excel/IXLWorksheet.cs b/ClosedXML/Excel/IXLWorksheet.cs
index cee3feb..7117744 100644
--- a/ClosedXML/Excel/IXLWorksheet.cs
+++ b/ClosedXML/Excel/IXLWorksheet.cs
@@ -1,6 +1,5 @@
using ClosedXML.Excel.Drawings;
using System;
-using System.Collections.Generic;
using System.Drawing;
using System.IO;
@@ -433,22 +432,22 @@
String Author { get; set; }
- IList Pictures { get; }
+ IXLPictures Pictures { get; }
- Drawings.IXLPicture AddPicture(Stream stream);
+ IXLPicture AddPicture(Stream stream);
- Drawings.IXLPicture AddPicture(Stream stream, String name);
+ IXLPicture AddPicture(Stream stream, String name);
- Drawings.IXLPicture AddPicture(Stream stream, XLPictureFormat format);
+ IXLPicture AddPicture(Stream stream, XLPictureFormat format);
- Drawings.IXLPicture AddPicture(Stream stream, XLPictureFormat format, String name);
+ IXLPicture AddPicture(Stream stream, XLPictureFormat format, String name);
- Drawings.IXLPicture AddPicture(Bitmap bitmap);
+ IXLPicture AddPicture(Bitmap bitmap);
- Drawings.IXLPicture AddPicture(Bitmap bitmap, String name);
+ IXLPicture AddPicture(Bitmap bitmap, String name);
- Drawings.IXLPicture AddPicture(String imageFile);
+ IXLPicture AddPicture(String imageFile);
- Drawings.IXLPicture AddPicture(String imageFile, String name);
+ IXLPicture AddPicture(String imageFile, String name);
}
-}
+}
\ No newline at end of file
diff --git a/ClosedXML/Excel/XLWorksheet.cs b/ClosedXML/Excel/XLWorksheet.cs
index ade653e..ceb8cc7 100644
--- a/ClosedXML/Excel/XLWorksheet.cs
+++ b/ClosedXML/Excel/XLWorksheet.cs
@@ -50,6 +50,7 @@
RangeAddress.FirstAddress.Worksheet = this;
RangeAddress.LastAddress.Worksheet = this;
+ Pictures = new XLPictures(this);
NamedRanges = new XLNamedRanges(workbook);
SheetView = new XLSheetView();
Tables = new XLTables();
@@ -588,10 +589,12 @@
case XLPicturePlacement.FreeFloating:
newPic.MoveTo(picture.Left, picture.Top);
break;
+
case XLPicturePlacement.Move:
var newAddress = new XLAddress(targetSheet, picture.TopLeftCellAddress.RowNumber, picture.TopLeftCellAddress.ColumnNumber, false, false);
newPic.MoveTo(newAddress, picture.GetOffset(XLMarkerPosition.TopLeft));
break;
+
case XLPicturePlacement.MoveAndSize:
var newFromAddress = new XLAddress(targetSheet, picture.TopLeftCellAddress.RowNumber, picture.TopLeftCellAddress.ColumnNumber, false, false);
var newToAddress = new XLAddress(targetSheet, picture.BottomRightCellAddress.RowNumber, picture.BottomRightCellAddress.ColumnNumber, false, false);
@@ -990,8 +993,7 @@
Internals.Dispose();
- foreach (var picture in this.Pictures)
- picture.Dispose();
+ this.Pictures.ForEach(p => p.Dispose());
base.Dispose();
}
@@ -1519,80 +1521,46 @@
public String Author { get; set; }
- public IList Pictures { get; private set; } = new List();
-
- private String GetNextPictureName()
- {
- var pictureNumber = this.Pictures.Count;
- while (Pictures.Any(p => p.Name == $"Picture {pictureNumber}"))
- {
- pictureNumber++;
- }
- return $"Picture {pictureNumber}";
- }
+ public IXLPictures Pictures { get; private set; }
public IXLPicture AddPicture(Stream stream)
{
- var picture = new XLPicture(this, stream);
- Pictures.Add(picture);
- picture.Name = GetNextPictureName();
- return picture;
+ return Pictures.Add(stream);
}
public IXLPicture AddPicture(Stream stream, string name)
{
- var picture = AddPicture(stream);
- picture.Name = name;
- return picture;
+ return Pictures.Add(stream, name);
}
public Drawings.IXLPicture AddPicture(Stream stream, XLPictureFormat format)
{
- var picture = new XLPicture(this, stream, format);
- Pictures.Add(picture);
- picture.Name = GetNextPictureName();
- return picture;
+ return Pictures.Add(stream, format);
}
public IXLPicture AddPicture(Stream stream, XLPictureFormat format, string name)
{
- var picture = AddPicture(stream, format);
- picture.Name = name;
- return picture;
+ return Pictures.Add(stream, format, name);
}
public IXLPicture AddPicture(Bitmap bitmap)
{
- var picture = new XLPicture(this, bitmap);
- Pictures.Add(picture);
- picture.Name = GetNextPictureName();
- return picture;
+ return Pictures.Add(bitmap);
}
public IXLPicture AddPicture(Bitmap bitmap, string name)
{
- var picture = AddPicture(bitmap);
- this.Name = name;
- return picture;
+ return Pictures.Add(bitmap, name);
}
public IXLPicture AddPicture(string imageFile)
{
- using (var bitmap = Image.FromFile(imageFile) as Bitmap)
- {
- var picture = new XLPicture(this, bitmap);
- Pictures.Add(picture);
- picture.Name = GetNextPictureName();
- return picture;
- }
+ return Pictures.Add(imageFile);
}
public IXLPicture AddPicture(string imageFile, string name)
{
- var picture = AddPicture(imageFile);
- picture.Name = name;
- return picture;
+ return Pictures.Add(imageFile, name);
}
-
}
}
diff --git a/ClosedXML/Excel/XLWorksheetInternals.cs b/ClosedXML/Excel/XLWorksheetInternals.cs
index 4fc467e..04a73f7 100644
--- a/ClosedXML/Excel/XLWorksheetInternals.cs
+++ b/ClosedXML/Excel/XLWorksheetInternals.cs
@@ -1,10 +1,11 @@
using System;
+
namespace ClosedXML.Excel
{
- internal class XLWorksheetInternals: IDisposable
+ internal class XLWorksheetInternals : IDisposable
{
public XLWorksheetInternals(
- XLCellsCollection cellsCollection,
+ XLCellsCollection cellsCollection,
XLColumnsCollection columnsCollection,
XLRowsCollection rowsCollection,
XLRanges mergedRanges
diff --git a/ClosedXML/Excel/XLWorksheets.cs b/ClosedXML/Excel/XLWorksheets.cs
index 057d3b7..ea22509 100644
--- a/ClosedXML/Excel/XLWorksheets.cs
+++ b/ClosedXML/Excel/XLWorksheets.cs
@@ -14,7 +14,7 @@
private readonly XLWorkbook _workbook;
private readonly Dictionary _worksheets = new Dictionary();
- #endregion
+ #endregion Constructor
public HashSet Deleted = new HashSet();
@@ -25,7 +25,7 @@
_workbook = workbook;
}
- #endregion
+ #endregion Constructor
#region IEnumerable Members
@@ -34,7 +34,7 @@
return ((IEnumerable)_worksheets.Values).GetEnumerator();
}
- #endregion
+ #endregion IEnumerable Members
#region IXLWorksheets Members
@@ -166,7 +166,7 @@
Add(t);
}
- #endregion
+ #endregion IXLWorksheets Members
public void Rename(String oldSheetName, String newSheetName)
{
diff --git a/ClosedXML/Extensions.cs b/ClosedXML/Extensions.cs
index 7483968..747961f 100644
--- a/ClosedXML/Extensions.cs
+++ b/ClosedXML/Extensions.cs
@@ -311,6 +311,18 @@
}
}
+ public static class ListExtensions
+ {
+ public static void RemoveAll(this IList list, Func predicate)
+ {
+ var indices = list.Where(item => predicate(item)).Select((item, i) => i).OrderByDescending(i => i).ToList();
+ foreach (var i in indices)
+ {
+ list.RemoveAt(i);
+ }
+ }
+ }
+
public static class DoubleValueExtensions
{
public static DoubleValue SaveRound(this DoubleValue value)
diff --git a/ClosedXML_Examples/ImageHandling/ImageAnchors.cs b/ClosedXML_Examples/ImageHandling/ImageAnchors.cs
index b71f37d..b20b42d 100644
--- a/ClosedXML_Examples/ImageHandling/ImageAnchors.cs
+++ b/ClosedXML_Examples/ImageHandling/ImageAnchors.cs
@@ -70,6 +70,11 @@
ws.AddPicture(fs, XLPictureFormat.Jpeg)
.MoveTo(100, 100)
.WithPlacement(XLPicturePlacement.FreeFloating);
+
+ // Add and delete picture immediately
+ ws.AddPicture(fs, XLPictureFormat.Jpeg)
+ .MoveTo(100, 600)
+ .Delete();
}
wb.SaveAs(filePath);
diff --git a/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs b/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs
index 3750ca3..b0ba0d6 100644
--- a/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs
+++ b/ClosedXML_Tests/Excel/ImageHandling/PictureTests.cs
@@ -1,6 +1,7 @@
using ClosedXML.Excel;
using ClosedXML.Excel.Drawings;
using NUnit.Framework;
+using System;
using System.Drawing;
using System.IO;
using System.Linq;
@@ -194,5 +195,41 @@
Assert.AreEqual(2, copy.Pictures.Count);
}
}
+
+ [Test]
+ public void CanDeletePictures()
+ {
+ using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Examples\ImageHandling\ImageAnchors.xlsx")))
+ using (var wb = new XLWorkbook(stream))
+ {
+ var ws = wb.Worksheets.First();
+ ws.Pictures.Delete(ws.Pictures.First());
+
+ var pictureName = ws.Pictures.First().Name;
+ ws.Pictures.Delete(pictureName);
+ }
+ }
+
+ [Test]
+ public void PictureRenameTests()
+ {
+ using (var stream = TestHelper.GetStreamFromResource(TestHelper.GetResourcePath(@"Examples\ImageHandling\ImageAnchors.xlsx")))
+ using (var wb = new XLWorkbook(stream))
+ {
+ var ws = wb.Worksheet("Images3");
+ var picture = ws.Pictures.First();
+ Assert.AreEqual("Picture 1", picture.Name);
+
+ picture.Name = "picture 1";
+ picture.Name = "pICture 1";
+ picture.Name = "Picture 1";
+
+ picture = ws.Pictures.Last();
+ picture.Name = "new name";
+
+ Assert.Throws(() => picture.Name = "Picture 1");
+ Assert.Throws(() => picture.Name = "picTURE 1");
+ }
+ }
}
}