diff --git a/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj b/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj index 80bba6a..044e497 100644 --- a/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj +++ b/ClosedXML/ClosedXML/ClosedXML/ClosedXML.csproj @@ -155,6 +155,7 @@ + diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs index 5a8eed3..e52ea8d 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Cells/XLCell.cs @@ -962,7 +962,7 @@ // MS Excel uses Tahoma 8 Swiss no matter what current style font // var style = GetStyleForRead(); var defaultFont = new XLFont() { FontName = "Tahoma", FontSize = 8, FontFamilyNumbering = XLFontFamilyNumberingValues.Swiss }; - _comment = new XLComment(defaultFont); + _comment = new XLComment(Worksheet, defaultFont); } return _comment; @@ -1447,7 +1447,7 @@ _dataType = source._dataType; FormulaR1C1 = source.FormulaR1C1; _richText = source._richText == null ? null : new XLRichText(source._richText, source.Style.Font); - _comment = source._comment == null ? null : new XLComment(source._comment, source.Style.Font); + _comment = source._comment == null ? null : new XLComment(source.Worksheet, source._comment, source.Style.Font); } public IXLCell CopyFrom(XLCell otherCell) @@ -1455,7 +1455,7 @@ var source = otherCell; _cellValue = source._cellValue; _richText = source._richText == null ? null : new XLRichText(source._richText, source.Style.Font); - _comment = source._comment == null ? null : new XLComment(source._comment, source.Style.Font); + _comment = source._comment == null ? null : new XLComment(source.Worksheet, source._comment, source.Style.Font); _dataType = source._dataType; FormulaR1C1 = source.FormulaR1C1; diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Charts/XLChart.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Charts/XLChart.cs index 6edee88..75de711 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Charts/XLChart.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Charts/XLChart.cs @@ -20,7 +20,7 @@ else zOrder = 1; ZOrder = zOrder; - Id = zOrder; + ShapeId = worksheet.Workbook.ShapeIdManager.GetNext(); RightAngleAxes = true; } diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Comments/XLComment.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Comments/XLComment.cs index 436cd9b..181ea72 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Comments/XLComment.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Comments/XLComment.cs @@ -7,26 +7,26 @@ { internal class XLComment : XLFormattedText, IXLComment { - - public XLComment(IXLFontBase defaultFont) + XLWorksheet _worksheet; + public XLComment(XLWorksheet worksheet, IXLFontBase defaultFont) : base(defaultFont) { - Initialize(); + Initialize(worksheet); } - public XLComment(XLFormattedText defaultComment, IXLFontBase defaultFont) + public XLComment(XLWorksheet worksheet, XLFormattedText defaultComment, IXLFontBase defaultFont) : base(defaultComment, defaultFont) { - Initialize(); + Initialize(worksheet); } - public XLComment(String text, IXLFontBase defaultFont) + public XLComment(XLWorksheet worksheet, String text, IXLFontBase defaultFont) : base(text, defaultFont) { - Initialize(); + Initialize(worksheet); } - private void Initialize() + private void Initialize(XLWorksheet worksheet) { Container = this; Anchor = XLDrawingAnchor.MoveAndSizeWithCells; @@ -34,6 +34,8 @@ Style.Size.Height = 4; // I think this is misused for legacy drawing Style.Size.Width = 2; SetVisible(); + _worksheet = worksheet; + ShapeId = worksheet.Workbook.ShapeIdManager.GetNext(); } public String Author { get; set; } @@ -64,7 +66,7 @@ #region IXLDrawing - public Int32 Id { get; internal set; } + public Int32 ShapeId { get; internal set; } public Boolean Hidden { get; set; } public IXLComment SetHidden() diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/IXLAddress.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/IXLAddress.cs index dd26417..48ad531 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/IXLAddress.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/IXLAddress.cs @@ -17,5 +17,6 @@ String ToStringFixed(XLReferenceStyle referenceStyle); String ToStringFixed(XLReferenceStyle referenceStyle, Boolean includeSheet); String ToString(XLReferenceStyle referenceStyle); + String UniqueId { get; } } } diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/XLAddress.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/XLAddress.cs index 9aa3821..d19a005 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/XLAddress.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Coordinates/XLAddress.cs @@ -373,5 +373,6 @@ return address; } + public String UniqueId { get { return RowNumber.ToString("0000000") + ColumnNumber.ToString("00000"); } } } } \ No newline at end of file diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/IXLDrawing.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/IXLDrawing.cs index ee09244..5884a22 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/IXLDrawing.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/IXLDrawing.cs @@ -5,7 +5,7 @@ public enum XLDrawingAnchor { MoveAndSizeWithCells, MoveWithCells, Absolute} public interface IXLDrawing { - Int32 Id { get; } + Int32 ShapeId { get; } Boolean Hidden { get; set; } T SetHidden(); diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/XLDrawing.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/XLDrawing.cs index 005d000..60cb1a8 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/XLDrawing.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Drawings/XLDrawing.cs @@ -11,7 +11,7 @@ Style = new XLDrawingStyle(); } - public Int32 Id { get; internal set; } + public Int32 ShapeId { get; internal set; } public Boolean Hidden { get; set; } public T SetHidden() @@ -158,5 +158,6 @@ } public IXLDrawingStyle Style { get; private set; } + } } diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/Misc/XLIdManager.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/Misc/XLIdManager.cs new file mode 100644 index 0000000..1c8c7fd --- /dev/null +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/Misc/XLIdManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ClosedXML.Excel +{ + internal class XLIdManager + { + private HashSet _hash = new HashSet(); + + + public Int32 GetNext() + { + if (_hash.Count == 0) + { + _hash.Add(1); + return 1; + } + + Int32 id = 1; + while (true) + { + if (!_hash.Contains(id)) + { + _hash.Add(id); + return id; + } + id++; + } + } + public void Add(Int32 value) + { + _hash.Add(value); + } + public void Add(IEnumerable values) + { + values.ForEach(v => _hash.Add(v)); + } + } +} diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook.cs index f038660..4598934 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook.cs @@ -441,7 +441,7 @@ } } - internal void CopyStream(Stream input, Stream output) + internal static void CopyStream(Stream input, Stream output) { var buffer = new byte[8 * 1024]; int len; @@ -537,6 +537,7 @@ WorksheetsInternal = new XLWorksheets(this); NamedRanges = new XLNamedRanges(this); CustomProperties = new XLCustomProperties(this); + ShapeIdManager = new XLIdManager(); } /// @@ -598,5 +599,7 @@ retVal.Add(r); return retVal; } + + internal XLIdManager ShapeIdManager { get; private set; } } } \ No newline at end of file diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Load.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Load.cs index 3fa2d0e..f68fe34 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Load.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Load.cs @@ -53,6 +53,7 @@ private void LoadSpreadsheetDocument(SpreadsheetDocument dSpreadsheet) { + ShapeIdManager = new XLIdManager(); SetProperties(dSpreadsheet); //var sharedStrings = dSpreadsheet.WorkbookPart.SharedStringTablePart.SharedStringTable.Elements(); SharedStringItem[] sharedStrings = null; @@ -195,6 +196,8 @@ LoadRowBreaks((RowBreaks)reader.LoadCurrentElement(), ws); else if (reader.ElementType == typeof(ColumnBreaks)) LoadColumnBreaks((ColumnBreaks)reader.LoadCurrentElement(), ws); + else if (reader.ElementType == typeof(LegacyDrawing)) + ws.LegacyDrawingId = (reader.LoadCurrentElement() as LegacyDrawing).Id.Value; } reader.Close(); @@ -275,7 +278,11 @@ foreach (Comment c in comments) { // find cell by reference var cell = ws.Cell(c.Reference); - cell.Comment.Author = authors[(int)c.AuthorId.Value].InnerText; + XLComment xlComment = cell.Comment as XLComment; + xlComment.Author = authors[(int)c.AuthorId.Value].InnerText; + //xlComment.ShapeId = (Int32)c.ShapeId.Value; + //ShapeIdManager.Add(xlComment.ShapeId); + var runs = c.GetFirstChild().Elements(); foreach (Run run in runs) { var runProperties = run.RunProperties; diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.NestedTypes.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.NestedTypes.cs index 51bf44b..174ac4e 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.NestedTypes.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.NestedTypes.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace ClosedXML.Excel { @@ -141,5 +142,9 @@ public IXLStyle Style; } #endregion + + + + } } \ No newline at end of file diff --git a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.cs index 0a6ca14..0f1bfa8 100644 --- a/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.cs +++ b/ClosedXML/ClosedXML/ClosedXML/Excel/XLWorkbook_Save.cs @@ -31,6 +31,9 @@ using Text = DocumentFormat.OpenXml.Spreadsheet.Text; using TopBorder = DocumentFormat.OpenXml.Spreadsheet.TopBorder; using Underline = DocumentFormat.OpenXml.Spreadsheet.Underline; +using System.Xml; +using System.Xml.Linq; +using System.Text; namespace ClosedXML.Excel @@ -144,17 +147,22 @@ context.RelIdGenerator.AddValues(worksheetPart.Parts.Select(p => p.RelationshipId).ToList(), RelType.Worksheet); // delete comment related parts (todo: review) - worksheetPart.DeletePart(worksheetPart.WorksheetCommentsPart); - worksheetPart.DeleteParts(worksheetPart.GetPartsOfType()); - + DeleteComments(worksheetPart, worksheet, context); + if (worksheet.Internals.CellsCollection.GetCells(c => c.HasComment).Any()) { WorksheetCommentsPart worksheetCommentsPart = worksheetPart.AddNewPart(context.RelIdGenerator.GetNext(RelType.Worksheet)); GenerateWorksheetCommentsPartContent(worksheetCommentsPart, worksheet); - worksheet.LegacyDrawingId = context.RelIdGenerator.GetNext(RelType.Worksheet); - VmlDrawingPart vmlDrawingPart = worksheetPart.AddNewPart(worksheet.LegacyDrawingId); + //VmlDrawingPart vmlDrawingPart = worksheetPart.AddNewPart(worksheet.LegacyDrawingId); + VmlDrawingPart vmlDrawingPart = worksheetPart.VmlDrawingParts.FirstOrDefault(); + if (vmlDrawingPart == null) + { + if (StringExtensions.IsNullOrWhiteSpace(worksheet.LegacyDrawingId)) + worksheet.LegacyDrawingId = context.RelIdGenerator.GetNext(RelType.Worksheet); + vmlDrawingPart = worksheetPart.AddNewPart(worksheet.LegacyDrawingId); + } GenerateVmlDrawingPartContent(vmlDrawingPart, worksheet, context); } @@ -196,6 +204,43 @@ SetPackageProperties(document); } + private void DeleteComments(WorksheetPart worksheetPart, XLWorksheet worksheet, SaveContext context) + { + // We have the comments so we can delete the comments part + worksheetPart.DeletePart(worksheetPart.WorksheetCommentsPart); + var vmlDrawingPart = worksheetPart.VmlDrawingParts.FirstOrDefault(); + + // Only delete the VmlDrawingParts for comments. + //foreach (VmlDrawingPart vmlDrawingPart in vmlDrawingParts) + if (vmlDrawingPart != null) + { + XDocument xdoc = XDocument.Load(vmlDrawingPart.GetStream(FileMode.Open)); + xdoc.Root.Elements().Where(e => e.Name.LocalName == "shapelayout").Remove(); + xdoc.Root.Elements().Where(e=>e.Name.LocalName == "shapetype" && (string)e.Attribute("id") == @"_x0000_t202").Remove(); + xdoc.Root.Elements().Where(e => e.Name.LocalName == "shape" && (string)e.Attribute("type") == @"#_x0000_t202").Remove(); + var imageParts = vmlDrawingPart.ImageParts.ToList(); + var legacyParts = vmlDrawingPart.LegacyDiagramTextParts.ToList(); + worksheetPart.ChangeIdOfPart(vmlDrawingPart, "xxRRxx"); // Anything will do for the new relationship id + // we just want it alive enough to create the copy + + Boolean hasShapes = xdoc.Root.Elements().Where(e => e.Name.LocalName == "shape").Any(); + if (imageParts.Count > 0 || legacyParts.Count > 0 || hasShapes) + { + if (StringExtensions.IsNullOrWhiteSpace(worksheet.LegacyDrawingId)) + worksheet.LegacyDrawingId = context.RelIdGenerator.GetNext(RelType.Worksheet); + VmlDrawingPart vmlDrawingPartNew = worksheetPart.AddNewPart(worksheet.LegacyDrawingId); + + if (hasShapes) + xdoc.Save(vmlDrawingPartNew.GetStream(System.IO.FileMode.Create)); + + imageParts.ForEach(p => vmlDrawingPartNew.AddPart(p, vmlDrawingPart.GetIdOfPart(p))); + legacyParts.ForEach(p => vmlDrawingPartNew.AddPart(p, vmlDrawingPart.GetIdOfPart(p))); + } + + worksheetPart.DeletePart(vmlDrawingPart); + } + } + private static void GenerateTables(XLWorksheet worksheet, WorksheetPart worksheetPart, SaveContext context) { worksheetPart.Worksheet.RemoveAllChildren(); @@ -4330,18 +4375,64 @@ // #endregion - System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(vmlDrawingPart.GetStream(System.IO.FileMode.Create), System.Text.Encoding.UTF8); + //XDocument xdoc; + //if (vmlDrawingPart.GetStream(FileMode.Open).Length > 0) + // xdoc = XDocument.Load(vmlDrawingPart.GetStream(FileMode.Open)); + //else + // xdoc = new XDocument(new XElement("xml")); + + ////xdoc.AddFirst(new XElement("xml")); + //const string shapeTypeId = "_x0000_t202"; // arbitrary, assigned by office + //xdoc.Root.Add( + // // v:shapetype + // new Vml.Shapetype( + // new Vml.Stroke() { JoinStyle = Vml.StrokeJoinStyleValues.Miter }, + // new Vml.Path() { AllowGradientShape = true, ConnectionPointType = Vml.Office.ConnectValues.Rectangle } + // ) + // { + // Id = shapeTypeId, + // CoordinateSize = "21600,21600", + // OptionalNumber = 202, + // EdgePath = "m,l,21600r21600,l21600,xe", + // } + // ) ; + + //// v:shape + //var cellWithComments = xlWorksheet.Internals.CellsCollection.GetCells().Where(c => c.HasComment); + + //foreach (XLCell c in cellWithComments) + //{ + // xdoc.Root.Add( + // GenerateShape(c, shapeTypeId) + // ); + //} + + //xdoc.Save(vmlDrawingPart.GetStream(System.IO.FileMode.Create), ); + //////////////////////////////// + var ms = new MemoryStream(); + CopyStream(vmlDrawingPart.GetStream(FileMode.OpenOrCreate), ms); + ms.Position = 0; + XmlTextReader reader = new XmlTextReader(ms); + XmlTextWriter writer = new XmlTextWriter(vmlDrawingPart.GetStream(FileMode.Create), Encoding.UTF8); + //if (ms.Length == 0) + // writer.WriteStartElement("xml"); + //else + //{ + // //ms.Position = 0; + // CopyXml(reader, writer); + //} + writer.WriteStartElement("xml"); - // o:shapelayout - new Vml.Office.ShapeLayout( - new Vml.Office.ShapeIdMap() - { - Extension = Vml.ExtensionHandlingBehaviorValues.Edit, - Data = "1" - } - ) { Extension = Vml.ExtensionHandlingBehaviorValues.Edit } - .WriteTo(writer); + //// o:shapelayout + //new Vml.Office.ShapeLayout( + // new Vml.Office.ShapeIdMap() + // { + // Extension = Vml.ExtensionHandlingBehaviorValues.Edit, + // Data = "1" + // } + // ) { Extension = Vml.ExtensionHandlingBehaviorValues.Edit } + // .WriteTo(writer); const string shapeTypeId = "_x0000_t202"; // arbitrary, assigned by office @@ -4366,11 +4457,88 @@ GenerateShape(c, shapeTypeId).WriteTo(writer); } + if (ms.Length > 0) + { + //ms.Position = 0; + //var sb = new StringBuilder(); + //while (reader.Read()) + // sb.Append(reader.ReadString()); + //var t = sb.ToString(); + CopyXml(reader, writer); + } + + //writer.WriteEndElement(); writer.Flush(); writer.Close(); } + static void CopyXml(XmlTextReader xtr, XmlTextWriter xtw) + { + string docElemName = null; + bool b = true; + while (b) + { + xtr.Read(); + switch (xtr.NodeType) + { + case XmlNodeType.Attribute: + xtw.WriteAttributeString(xtr.Prefix, + xtr.LocalName, xtr.NamespaceURI, xtr.Value); + break; + case XmlNodeType.CDATA: + xtw.WriteCData(xtr.Value); + break; + case XmlNodeType.Comment: + xtw.WriteComment(xtr.Value); + break; + case XmlNodeType.DocumentType: + xtw.WriteDocType(xtr.Name, null, null, null); + break; + case XmlNodeType.Element: + if (xtr.LocalName != "xml") + { + xtw.WriteNode(xtr, true); + //xtw.WriteStartElement(xtr.Prefix, + // xtr.LocalName, xtr.NamespaceURI); + //if (xtr.IsEmptyElement) + // xtw.WriteEndElement(); + if (docElemName == null) + docElemName = xtr.Name; + } + break; + case XmlNodeType.EndElement: + if (docElemName == xtr.Name) + b = false; + else + xtw.WriteEndElement(); + break; + case XmlNodeType.EntityReference: + xtw.WriteEntityRef(xtr.Name); + break; + case XmlNodeType.ProcessingInstruction: + xtw.WriteProcessingInstruction(xtr.Name, xtr.Value); + break; + case XmlNodeType.SignificantWhitespace: + xtw.WriteWhitespace(xtr.Value); + break; + case XmlNodeType.Text: + xtw.WriteString(xtr.Value); + break; + case XmlNodeType.Whitespace: + xtw.WriteWhitespace(xtr.Value); + break; + case XmlNodeType.XmlDeclaration: + //xtw.WriteStartDocument(); + break; + default: + if (docElemName == xtr.Name) + b = false; + break; + } + } + } + // VML Shape for Comment private static Vml.Shape GenerateShape(XLCell c, string shapeTypeId) { @@ -4406,7 +4574,8 @@ var bottomRow = topRow + c.Comment.Style.Size.Height; var bottomOffset = rowNumber == 1 ? 2 : 9; - var shapeId = string.Format("_x0000_s{0}", c.GetHashCode().ToString()); // Unique per cell, e.g.: "_x0000_s1026" + + String shapeId = String.Format("_x0000_s{0}", c.Comment.ShapeId); // Unique per cell (workbook?), e.g.: "_x0000_s1026" return new Vml.Shape( new Vml.Fill { Color2 = "#" + c.Comment.Style.ColorsAndLines.FillColor.Color.ToHex().Substring(2) },