| /******************************************************************************* | |
| * 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 | |
| { | |
| /// <summary> | |
| /// Collection for Drawing objects. | |
| /// </summary> | |
| public class ExcelDrawings : IEnumerable<ExcelDrawing>, IDisposable | |
| { | |
| private XmlDocument _drawingsXml=new XmlDocument(); | |
| private Dictionary<string, int> _drawingNames; | |
| private List<ExcelDrawing> _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<ImageCompare> _pics = new List<ImageCompare>(); | |
| internal Dictionary<string, string> _hashes = new Dictionary<string, string>(); | |
| internal ExcelPackage _package; | |
| internal Packaging.ZipPackageRelationship _drawingRelation = null; | |
| internal ExcelDrawings(ExcelPackage xlPackage, ExcelWorksheet sheet) | |
| { | |
| _drawingsXml = new XmlDocument(); | |
| _drawingsXml.PreserveWhitespace = false; | |
| _drawings = new List<ExcelDrawing>(); | |
| _drawingNames = new Dictionary<string,int>(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; } | |
| /// <summary> | |
| /// A reference to the drawing xml document | |
| /// </summary> | |
| 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 | |
| /// <summary> | |
| /// Creates the NamespaceManager. | |
| /// </summary> | |
| 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); | |
| } | |
| /// <summary> | |
| /// Provides access to a namespace manager instance to allow XPath searching | |
| /// </summary> | |
| XmlNamespaceManager _nsManager=null; | |
| public XmlNamespaceManager NameSpaceManager | |
| { | |
| get | |
| { | |
| return _nsManager; | |
| } | |
| } | |
| #endregion | |
| #region IEnumerable Members | |
| public IEnumerator GetEnumerator() | |
| { | |
| return (_drawings.GetEnumerator()); | |
| } | |
| #region IEnumerable<ExcelDrawing> Members | |
| IEnumerator<ExcelDrawing> IEnumerable<ExcelDrawing>.GetEnumerator() | |
| { | |
| return (_drawings.GetEnumerator()); | |
| } | |
| #endregion | |
| /// <summary> | |
| /// Returns the drawing at the specified position. | |
| /// </summary> | |
| /// <param name="PositionID">The position of the drawing. 0-base</param> | |
| /// <returns></returns> | |
| public ExcelDrawing this[int PositionID] | |
| { | |
| get | |
| { | |
| return (_drawings[PositionID]); | |
| } | |
| } | |
| /// <summary> | |
| /// Returns the drawing matching the specified name | |
| /// </summary> | |
| /// <param name="Name">The name of the worksheet</param> | |
| /// <returns></returns> | |
| 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 | |
| /// <summary> | |
| /// Add a new chart to the worksheet. | |
| /// Do not support Bubble-, Radar-, Stock- or Surface charts. | |
| /// </summary> | |
| /// <param name="Name"></param> | |
| /// <param name="ChartType">Type of chart</param> | |
| /// <param name="PivotTableSource">The pivottable source for a pivotchart</param> | |
| /// <returns>The chart</returns> | |
| 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; | |
| } | |
| /// <summary> | |
| /// Add a new chart to the worksheet. | |
| /// Do not support Bubble-, Radar-, Stock- or Surface charts. | |
| /// </summary> | |
| /// <param name="Name"></param> | |
| /// <param name="ChartType">Type of chart</param> | |
| /// <returns>The chart</returns> | |
| public ExcelChart AddChart(string Name, eChartType ChartType) | |
| { | |
| return AddChart(Name, ChartType, null); | |
| } | |
| /// <summary> | |
| /// Add a picure to the worksheet | |
| /// </summary> | |
| /// <param name="Name"></param> | |
| /// <param name="image">An image. Allways saved in then JPeg format</param> | |
| /// <returns></returns> | |
| public ExcelPicture AddPicture(string Name, Image image) | |
| { | |
| return AddPicture(Name, image, null); | |
| } | |
| /// <summary> | |
| /// Add a picure to the worksheet | |
| /// </summary> | |
| /// <param name="Name"></param> | |
| /// <param name="image">An image. Allways saved in then JPeg format</param> | |
| /// <param name="Hyperlink">Picture Hyperlink</param> | |
| /// <returns></returns> | |
| 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")); | |
| } | |
| /// <summary> | |
| /// Add a picure to the worksheet | |
| /// </summary> | |
| /// <param name="Name"></param> | |
| /// <param name="ImageFile">The image file</param> | |
| /// <returns></returns> | |
| public ExcelPicture AddPicture(string Name, FileInfo ImageFile) | |
| { | |
| return AddPicture(Name, ImageFile, null); | |
| } | |
| /// <summary> | |
| /// Add a picure to the worksheet | |
| /// </summary> | |
| /// <param name="Name"></param> | |
| /// <param name="ImageFile">The image file</param> | |
| /// <param name="Hyperlink">Picture Hyperlink</param> | |
| /// <returns></returns> | |
| 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")); | |
| } | |
| /// <summary> | |
| /// Add a new shape to the worksheet | |
| /// </summary> | |
| /// <param name="Name">Name</param> | |
| /// <param name="Style">Shape style</param> | |
| /// <returns>The shape object</returns> | |
| 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; | |
| } | |
| /// <summary> | |
| /// Add a new shape to the worksheet | |
| /// </summary> | |
| /// <param name="Name">Name</param> | |
| /// <param name="Source">Source shape</param> | |
| /// <returns>The shape object</returns> | |
| public ExcelShape AddShape(string Name, ExcelShape Source) | |
| { | |
| 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(); | |
| drawNode.InnerXml = Source.TopNode.InnerXml; | |
| ExcelShape shape = new ExcelShape(this, drawNode); | |
| shape.Name = Name; | |
| shape.Style = Source.Style; | |
| _drawings.Add(shape); | |
| _drawingNames.Add(Name, _drawings.Count - 1); | |
| return shape; | |
| } | |
| private XmlElement CreateDrawingXml() | |
| { | |
| if (DrawingXml.OuterXml == "") | |
| { | |
| DrawingXml.LoadXml(string.Format("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><xdr:wsDr xmlns:xdr=\"{0}\" xmlns:a=\"{1}\" />", 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 = "<xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>0</xdr:row><xdr:rowOff>0</xdr:rowOff>"; | |
| //Add to position Element; | |
| XmlElement toNode = _drawingsXml.CreateElement("xdr", "to", ExcelPackage.schemaSheetDrawings); | |
| drawNode.AppendChild(toNode); | |
| toNode.InnerXml = "<xdr:col>10</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>10</xdr:row><xdr:rowOff>0</xdr:rowOff>"; | |
| } | |
| return drawNode; | |
| } | |
| #endregion | |
| #region Remove methods | |
| /// <summary> | |
| /// Removes a drawing. | |
| /// </summary> | |
| /// <param name="Index">The index of the drawing</param> | |
| 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); | |
| } | |
| /// <summary> | |
| /// Removes a drawing. | |
| /// </summary> | |
| /// <param name="Drawing">The drawing</param> | |
| public void Remove(ExcelDrawing Drawing) | |
| { | |
| Remove(_drawingNames[Drawing.Name]); | |
| } | |
| /// <summary> | |
| /// Removes a drawing. | |
| /// </summary> | |
| /// <param name="Name">The name of the drawing</param> | |
| public void Remove(string Name) | |
| { | |
| Remove(_drawingNames[Name]); | |
| } | |
| /// <summary> | |
| /// Removes all drawings from the collection | |
| /// </summary> | |
| 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; | |
| } | |
| } | |
| } |