/*******************************************************************************
* You may amend and distribute as you like, but don't remove this header!
*
* EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
* See http://www.codeplex.com/EPPlus for details.
*
* Copyright (C) 2011 Jan Källman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
* If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
*
* All code and executables are provided "as is" with no warranty either express or implied.
* The author accepts no liability for any damage or loss of business that this product may cause.
*
* Code change notes:
*
* Author Change Date
* ******************************************************************************
* Jan Källman Initial Release 2009-12-22
* Jan Källman License changed GPL-->LGPL 2011-12-16
*******************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Collections;
using System.IO;
using System.Drawing;
using System.Linq;
using OfficeOpenXml.Drawing.Chart;
using OfficeOpenXml.Table.PivotTable;
using OfficeOpenXml.Utils;
namespace OfficeOpenXml.Drawing
{
///
/// Collection for Drawing objects.
///
public class ExcelDrawings : IEnumerable, IDisposable
{
private XmlDocument _drawingsXml=new XmlDocument();
private Dictionary _drawingNames;
private List _drawings;
internal class ImageCompare
{
internal byte[] image { get; set; }
internal string relID { get; set; }
internal bool Comparer(byte[] compareImg)
{
if (compareImg.Length != image.Length)
{
return false;
}
for (int i = 0; i < image.Length; i++)
{
if (image[i] != compareImg[i])
{
return false;
}
}
return true; //Equal
}
}
//internal List _pics = new List();
internal Dictionary _hashes = new Dictionary();
internal ExcelPackage _package;
internal Packaging.ZipPackageRelationship _drawingRelation = null;
internal ExcelDrawings(ExcelPackage xlPackage, ExcelWorksheet sheet)
{
_drawingsXml = new XmlDocument();
_drawingsXml.PreserveWhitespace = false;
_drawings = new List();
_drawingNames = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
_package = xlPackage;
Worksheet = sheet;
XmlNode node = sheet.WorksheetXml.SelectSingleNode("//d:drawing", sheet.NameSpaceManager);
CreateNSM();
if (node != null)
{
_drawingRelation = sheet.Part.GetRelationship(node.Attributes["r:id"].Value);
_uriDrawing = UriHelper.ResolvePartUri(sheet.WorksheetUri, _drawingRelation.TargetUri);
_part = xlPackage.Package.GetPart(_uriDrawing);
XmlHelper.LoadXmlSafe(_drawingsXml, _part.GetStream());
AddDrawings();
}
}
internal ExcelWorksheet Worksheet { get; set; }
///
/// A reference to the drawing xml document
///
public XmlDocument DrawingXml
{
get
{
return _drawingsXml;
}
}
private void AddDrawings()
{
// Look inside all children for the drawings because they could be inside
// Markup Compatibility AlternativeContent/Choice or AlternativeContent/Fallback nodes.
// The code below currently pretends that loading all Choice alternative drawings doesn't cause a problem
// elsewhere. This seems to be ok for the time being as encountered drawing files so far only seem to have
// one Choice node (and no Fallback) underneath the AlternativeContent node. (Excel 2013 that is.)
// This change prevents CodePlex issue #15028 from occurring.
// (the drawing xml part (that ONLY contained AlternativeContent nodes) was incorrectly being garbage collected when the package was saved)
XmlNodeList list = _drawingsXml.SelectNodes("//*[self::xdr:twoCellAnchor or self::xdr:oneCellAnchor or self::xdr:absoluteAnchor]", NameSpaceManager);
foreach (XmlNode node in list)
{
ExcelDrawing dr;
switch(node.LocalName)
{
case "oneCellAnchor":
dr = new ExcelDrawing(this, node, "xdr:sp/xdr:nvSpPr/xdr:cNvPr/@name");
break;
case "twoCellAnchor":
dr = ExcelDrawing.GetDrawing(this, node);
break;
case "absoluteAnchor":
dr = ExcelDrawing.GetDrawing(this, node);
break;
default: //"absoluteCellAnchor":
dr = null;
break;
}
if (dr != null)
{
_drawings.Add(dr);
if (!_drawingNames.ContainsKey(dr.Name))
{
_drawingNames.Add(dr.Name, _drawings.Count - 1);
}
}
}
}
#region NamespaceManager
///
/// Creates the NamespaceManager.
///
private void CreateNSM()
{
NameTable nt = new NameTable();
_nsManager = new XmlNamespaceManager(nt);
_nsManager.AddNamespace("a", ExcelPackage.schemaDrawings);
_nsManager.AddNamespace("xdr", ExcelPackage.schemaSheetDrawings);
_nsManager.AddNamespace("c", ExcelPackage.schemaChart);
_nsManager.AddNamespace("r", ExcelPackage.schemaRelationships);
}
///
/// Provides access to a namespace manager instance to allow XPath searching
///
XmlNamespaceManager _nsManager=null;
public XmlNamespaceManager NameSpaceManager
{
get
{
return _nsManager;
}
}
#endregion
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return (_drawings.GetEnumerator());
}
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return (_drawings.GetEnumerator());
}
#endregion
///
/// Returns the drawing at the specified position.
///
/// The position of the drawing. 0-base
///
public ExcelDrawing this[int PositionID]
{
get
{
return (_drawings[PositionID]);
}
}
///
/// Returns the drawing matching the specified name
///
/// The name of the worksheet
///
public ExcelDrawing this[string Name]
{
get
{
if (_drawingNames.ContainsKey(Name))
{
return _drawings[_drawingNames[Name]];
}
else
{
return null;
}
}
}
public int Count
{
get
{
if (_drawings == null)
{
return 0;
}
else
{
return _drawings.Count;
}
}
}
Packaging.ZipPackagePart _part=null;
internal Packaging.ZipPackagePart Part
{
get
{
return _part;
}
}
Uri _uriDrawing=null;
public Uri UriDrawing
{
get
{
return _uriDrawing;
}
}
#endregion
#region Add functions
///
/// Add a new chart to the worksheet.
/// Do not support Bubble-, Radar-, Stock- or Surface charts.
///
///
/// Type of chart
/// The pivottable source for a pivotchart
/// The chart
public ExcelChart AddChart(string Name, eChartType ChartType, ExcelPivotTable PivotTableSource)
{
if(_drawingNames.ContainsKey(Name))
{
throw new Exception("Name already exists in the drawings collection");
}
if (ChartType == eChartType.StockHLC ||
ChartType == eChartType.StockOHLC ||
ChartType == eChartType.StockVOHLC)
{
throw(new NotImplementedException("Chart type is not supported in the current version"));
}
if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
{
throw new InvalidOperationException("Chart Worksheets can't have more than one chart");
}
XmlElement drawNode = CreateDrawingXml();
ExcelChart chart = ExcelChart.GetNewChart(this, drawNode, ChartType, null, PivotTableSource);
chart.Name = Name;
_drawings.Add(chart);
_drawingNames.Add(Name, _drawings.Count - 1);
return chart;
}
///
/// Add a new chart to the worksheet.
/// Do not support Bubble-, Radar-, Stock- or Surface charts.
///
///
/// Type of chart
/// The chart
public ExcelChart AddChart(string Name, eChartType ChartType)
{
return AddChart(Name, ChartType, null);
}
///
/// Add a picure to the worksheet
///
///
/// An image. Allways saved in then JPeg format
///
public ExcelPicture AddPicture(string Name, Image image)
{
return AddPicture(Name, image, null);
}
///
/// Add a picure to the worksheet
///
///
/// An image. Allways saved in then JPeg format
/// Picture Hyperlink
///
public ExcelPicture AddPicture(string Name, Image image, Uri Hyperlink)
{
if (image != null)
{
if (_drawingNames.ContainsKey(Name))
{
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml();
drawNode.SetAttribute("editAs", "oneCell");
ExcelPicture pic = new ExcelPicture(this, drawNode, image, Hyperlink);
pic.Name = Name;
_drawings.Add(pic);
_drawingNames.Add(Name, _drawings.Count - 1);
return pic;
}
throw (new Exception("AddPicture: Image can't be null"));
}
///
/// Add a picure to the worksheet
///
///
/// The image file
///
public ExcelPicture AddPicture(string Name, FileInfo ImageFile)
{
return AddPicture(Name, ImageFile, null);
}
///
/// Add a picure to the worksheet
///
///
/// The image file
/// Picture Hyperlink
///
public ExcelPicture AddPicture(string Name, FileInfo ImageFile, Uri Hyperlink)
{
if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
{
throw new InvalidOperationException("Chart worksheets can't have more than one drawing");
}
if (ImageFile != null)
{
if (_drawingNames.ContainsKey(Name))
{
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml();
drawNode.SetAttribute("editAs", "oneCell");
ExcelPicture pic = new ExcelPicture(this, drawNode, ImageFile, Hyperlink);
pic.Name = Name;
_drawings.Add(pic);
_drawingNames.Add(Name, _drawings.Count - 1);
return pic;
}
throw (new Exception("AddPicture: ImageFile can't be null"));
}
///
/// Add a new shape to the worksheet
///
/// Name
/// Shape style
/// The shape object
public ExcelShape AddShape(string Name, eShapeStyle Style)
{
if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
{
throw new InvalidOperationException("Chart worksheets can't have more than one drawing");
}
if (_drawingNames.ContainsKey(Name))
{
throw new Exception("Name already exists in the drawings collection");
}
XmlElement drawNode = CreateDrawingXml();
ExcelShape shape = new ExcelShape(this, drawNode, Style);
shape.Name = Name;
shape.Style = Style;
_drawings.Add(shape);
_drawingNames.Add(Name, _drawings.Count - 1);
return shape;
}
private XmlElement CreateDrawingXml()
{
if (DrawingXml.OuterXml == "")
{
DrawingXml.LoadXml(string.Format("", ExcelPackage.schemaSheetDrawings, ExcelPackage.schemaDrawings));
_uriDrawing = new Uri(string.Format("/xl/drawings/drawing{0}.xml", Worksheet.SheetID),UriKind.Relative);
Packaging.ZipPackage package = Worksheet._package.Package;
_part = package.CreatePart(_uriDrawing, "application/vnd.openxmlformats-officedocument.drawing+xml", _package.Compression);
StreamWriter streamChart = new StreamWriter(_part.GetStream(FileMode.Create, FileAccess.Write));
DrawingXml.Save(streamChart);
streamChart.Close();
package.Flush();
_drawingRelation = Worksheet.Part.CreateRelationship(UriHelper.GetRelativeUri(Worksheet.WorksheetUri, _uriDrawing), Packaging.TargetMode.Internal, ExcelPackage.schemaRelationships + "/drawing");
XmlElement e = Worksheet.WorksheetXml.CreateElement("drawing", ExcelPackage.schemaMain);
e.SetAttribute("id",ExcelPackage.schemaRelationships, _drawingRelation.Id);
Worksheet.WorksheetXml.DocumentElement.AppendChild(e);
package.Flush();
}
XmlNode colNode = _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
XmlElement drawNode;
if (this.Worksheet is ExcelChartsheet)
{
drawNode = _drawingsXml.CreateElement("xdr", "absoluteAnchor", ExcelPackage.schemaSheetDrawings);
XmlElement posNode = _drawingsXml.CreateElement("xdr", "pos", ExcelPackage.schemaSheetDrawings);
posNode.SetAttribute("y", "0");
posNode.SetAttribute("x", "0");
drawNode.AppendChild(posNode);
XmlElement extNode = _drawingsXml.CreateElement("xdr", "ext", ExcelPackage.schemaSheetDrawings);
extNode.SetAttribute("cy", "6072876");
extNode.SetAttribute("cx", "9299263");
drawNode.AppendChild(extNode);
colNode.AppendChild(drawNode);
}
else
{
drawNode = _drawingsXml.CreateElement("xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
colNode.AppendChild(drawNode);
//Add from position Element;
XmlElement fromNode = _drawingsXml.CreateElement("xdr", "from", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(fromNode);
fromNode.InnerXml = "0000";
//Add to position Element;
XmlElement toNode = _drawingsXml.CreateElement("xdr", "to", ExcelPackage.schemaSheetDrawings);
drawNode.AppendChild(toNode);
toNode.InnerXml = "100100";
}
return drawNode;
}
#endregion
#region Remove methods
///
/// Removes a drawing.
///
/// The index of the drawing
public void Remove(int Index)
{
if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
{
throw new InvalidOperationException("Can' remove charts from chart worksheets");
}
RemoveDrawing(Index);
}
internal void RemoveDrawing(int Index)
{
var draw = _drawings[Index];
draw.DeleteMe();
for (int i = Index + 1; i < _drawings.Count; i++)
{
_drawingNames[_drawings[i].Name]--;
}
_drawingNames.Remove(draw.Name);
_drawings.Remove(draw);
}
///
/// Removes a drawing.
///
/// The drawing
public void Remove(ExcelDrawing Drawing)
{
Remove(_drawingNames[Drawing.Name]);
}
///
/// Removes a drawing.
///
/// The name of the drawing
public void Remove(string Name)
{
Remove(_drawingNames[Name]);
}
///
/// Removes all drawings from the collection
///
public void Clear()
{
if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
{
throw new InvalidOperationException("Can' remove charts from chart worksheets");
}
ClearDrawings();
}
internal void ClearDrawings()
{
while (Count > 0)
{
RemoveDrawing(0);
}
}
#endregion
internal void AdjustWidth(int[,] pos)
{
var ix = 0;
//Now set the size for all drawings depending on the editAs property.
foreach (OfficeOpenXml.Drawing.ExcelDrawing d in this)
{
if (d.EditAs != Drawing.eEditAs.TwoCell)
{
if (d.EditAs == Drawing.eEditAs.Absolute)
{
d.SetPixelLeft(pos[ix, 0]);
}
d.SetPixelWidth(pos[ix, 1]);
}
ix++;
}
}
internal void AdjustHeight(int[,] pos)
{
var ix = 0;
//Now set the size for all drawings depending on the editAs property.
foreach (OfficeOpenXml.Drawing.ExcelDrawing d in this)
{
if (d.EditAs != Drawing.eEditAs.TwoCell)
{
if (d.EditAs == Drawing.eEditAs.Absolute)
{
d.SetPixelTop(pos[ix, 0]);
}
d.SetPixelHeight(pos[ix, 1]);
}
ix++;
}
}
internal int[,] GetDrawingWidths()
{
int[,] pos = new int[Count, 2];
int ix = 0;
//Save the size for all drawings
foreach (ExcelDrawing d in this)
{
pos[ix, 0] = d.GetPixelLeft();
pos[ix++, 1] = d.GetPixelWidth();
}
return pos;
}
internal int[,] GetDrawingHeight()
{
int[,] pos = new int[Count, 2];
int ix = 0;
//Save the size for all drawings
foreach (ExcelDrawing d in this)
{
pos[ix, 0] = d.GetPixelTop();
pos[ix++, 1] = d.GetPixelHeight();
}
return pos;
}
public void Dispose()
{
_drawingsXml = null;
_hashes.Clear();
_hashes = null;
_part = null;
_drawingNames.Clear();
_drawingNames = null;
_drawingRelation = null;
foreach (var d in _drawings)
{
d.Dispose();
}
_drawings.Clear();
_drawings = null;
}
}
}