/******************************************************************************* * 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; } } }