Пример изображения и диаграммы в Excel отчете MS Dynamics AX 2012, на основе библиотек Open XML SDK.
Вставка изображения.
Исходный код на C# был сгенерирован программой OpenXMLSDKToolV25 с https://www.microsoft.com/en-US/download/details.aspx?id=30425 из файлов Excel созданных вручную.
В коде x++ были оставлены только обязательные элементы ImagePart, без которых при открытии вновь созданного отчета выходит ошибка Method ‘add’ in COM object of class ‘Workbooks’ returned error code 0x800A03EC (<unknown>) which means: Add method of Workbooks class failed. / Метод «add» в COM-объекте класса «Workbooks» возвратил код ошибки 0x800A03EC (<неизвестно>), который означает: Метод Add из класса Workbooks завершен неверно. Cannot create Microsoft Office Excel workbook. / Невозможно создать рабочую книгу Microsoft Office Excel.
Был создан для примера класс наследник XMLExcelReport_Standard_RU для вывода отчетов в Excel.
class XMLReport_Example extends XMLExcelReport_Standard_RU { DocumentFormat.OpenXml.Packaging.DrawingsPart drawingsPart; DocumentFormat.OpenXml.Drawing.Spreadsheet.WorksheetDrawing worksheetDrawing; DocumentFormat.OpenXml.Spreadsheet.Worksheet worksheet; DocumentFormat.OpenXml.Packaging.WorksheetPart worksheetPart; DocumentFormat.OpenXml.Spreadsheet.Drawing drawing; OXMLWorkBook_RU oxmlWorkBook_RU; }
Для того чтобы вставить изображение нужен объект DrawingsPart, который отвечает за упаковку раздела /xl/drawings, а также worksheetDrawing и drawing. Данные объекты в SpreadsheetDocument должны быть одни. Ниже пример добавления этих объектов в шаблон отчета
System.Type type; ; type = CLRInterop::getType('DocumentFormat.OpenXml.Packaging.DrawingsPart'); oxmlWorkBook_RU = document.workbook(); worksheetPart = oxmlWorkBook_RU.getWorksheet().worksheetPart(); worksheet = worksheetPart.get_Worksheet(); drawingsPart = OXML_RU::invokeGeneric( worksheetPart , 'AddNewPart' , type , new Array(Types::Class) , true); worksheetDrawing = new DocumentFormat.OpenXml.Drawing.Spreadsheet.WorksheetDrawing(); worksheetDrawing.AddNamespaceDeclaration("xdr", "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"); worksheetDrawing.AddNamespaceDeclaration("a", "http://schemas.openxmlformats.org/drawingml/2006/main"); drawing = new DocumentFormat.OpenXml.Spreadsheet.Drawing(); drawing.set_Id(new DocumentFormat.OpenXml.StringValue(worksheetPart.GetIdOfPart(drawingsPart))); OXML_RU::appendChild(worksheet, drawing);
А imagePart создается для каждого изображения отдельный, поэтому его создание было вынесено в отдельный метод, который вызывается из перекрытого метода fillBody[1].
public void insertImage( Filename _fileName , int64 _posX , int64 _posY , int64 _width , int64 _height ) { DocumentFormat.OpenXml.Packaging.ImagePart imagePart; DocumentFormat.OpenXml.Drawing.Spreadsheet.ShapeProperties shapeProperties; DocumentFormat.OpenXml.Drawing.Spreadsheet.NonVisualPictureProperties nonVisualPictureProperties; DocumentFormat.OpenXml.Drawing.Spreadsheet.NonVisualDrawingProperties nonVisualDrawingProperties; DocumentFormat.OpenXml.Drawing.Spreadsheet.BlipFill blipFill; DocumentFormat.OpenXml.Drawing.Spreadsheet.Position position; DocumentFormat.OpenXml.Drawing.Spreadsheet.Extent extent; DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture picture; DocumentFormat.OpenXml.Drawing.Spreadsheet.AbsoluteAnchor AbsoluteAnchor; DocumentFormat.OpenXml.Drawing.Stretch stretch; DocumentFormat.OpenXml.Drawing.Blip blip; DocumentFormat.OpenXml.Drawing.Transform2D transform2D; DocumentFormat.OpenXml.Drawing.Extents extents; DocumentFormat.OpenXml.Drawing.PresetGeometry presetGeometry; System.IO.FileStream fileStream; ; imagePart = drawingsPart.AddImagePart(DocumentFormat.OpenXml.Packaging.ImagePartType::Jpeg); fileStream = new System.IO.FileStream(_fileName, System.IO.FileMode::Open); imagePart.FeedData(fileStream); fileStream.Close(); nonVisualDrawingProperties = new DocumentFormat.OpenXml.Drawing.Spreadsheet.NonVisualDrawingProperties(); nonVisualDrawingProperties.set_Id(OXML_RU::setUInt32Value(i * 22)); nonVisualDrawingProperties.set_Name(new DocumentFormat.OpenXml.StringValue(strFmt("%1%2", 'Pic', i * 22))); nonVisualPictureProperties = new DocumentFormat.OpenXml.Drawing.Spreadsheet.NonVisualPictureProperties(); nonVisualPictureProperties.set_NonVisualDrawingProperties(nonVisualDrawingProperties); nonVisualPictureProperties.set_NonVisualPictureDrawingProperties(new DocumentFormat.OpenXml.Drawing.Spreadsheet.NonVisualPictureDrawingProperties()); stretch = new DocumentFormat.OpenXml.Drawing.Stretch(); stretch.set_FillRectangle(new DocumentFormat.OpenXml.Drawing.FillRectangle()); blip = new DocumentFormat.OpenXml.Drawing.Blip(); blip.set_Embed(new DocumentFormat.OpenXml.StringValue(drawingsPart.GetIdOfPart(imagePart))); blip.set_CompressionState(OXMLExp::getEnumValue(DocumentFormat.OpenXml.Drawing.BlipCompressionValues::Print)); blipFill = new DocumentFormat.OpenXml.Drawing.Spreadsheet.BlipFill(); blipFill.set_Blip(blip); blipFill.set_SourceRectangle(new DocumentFormat.OpenXml.Drawing.SourceRectangle()); OXML_RU::appendChild(blipFill, stretch); extents = new DocumentFormat.OpenXml.Drawing.Extents(); extents.set_Cx(new DocumentFormat.OpenXml.Int64Value(_width)); extents.set_Cy(new DocumentFormat.OpenXml.Int64Value(_height)); transform2D = new DocumentFormat.OpenXml.Drawing.Transform2D(); transform2D.set_Extents(extents); shapeProperties = new DocumentFormat.OpenXml.Drawing.Spreadsheet.ShapeProperties(); shapeProperties.set_BlackWhiteMode(OXMLExp::getEnumValue(DocumentFormat.OpenXml.Drawing.BlackWhiteModeValues::Auto)); shapeProperties.set_Transform2D(transform2D); presetGeometry = new DocumentFormat.OpenXml.Drawing.PresetGeometry(); presetGeometry.set_AdjustValueList(new DocumentFormat.OpenXml.Drawing.AdjustValueList()); presetGeometry.set_Preset(OXMLExp::getEnumValue(DocumentFormat.OpenXml.Drawing.ShapeTypeValues::Rectangle)); OXML_RU::appendChild(shapeProperties, presetGeometry); OXML_RU::appendChild(shapeProperties, new DocumentFormat.OpenXml.Drawing.NoFill()); picture = new DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture(); picture.set_NonVisualPictureProperties(nonVisualPictureProperties); picture.set_BlipFill(blipFill); picture.set_ShapeProperties(shapeProperties); position = new DocumentFormat.OpenXml.Drawing.Spreadsheet.Position(); position.set_X(new DocumentFormat.OpenXml.Int64Value(_posX)); position.set_Y(new DocumentFormat.OpenXml.Int64Value(_posY)); extent = new DocumentFormat.OpenXml.Drawing.Spreadsheet.Extent(); extent.set_Cx(extents.get_Cx()); extent.set_Cy(extents.get_Cy()); absoluteAnchor = new DocumentFormat.OpenXml.Drawing.Spreadsheet.AbsoluteAnchor(); absoluteAnchor.set_Position(position); absoluteAnchor.set_Extent(extent); OXML_RU::appendChild(absoluteAnchor, picture); OXML_RU::appendChild(absoluteAnchor, new DocumentFormat.OpenXml.Drawing.Spreadsheet.ClientData()); OXML_RU::appendChild(worksheetDrawing, absoluteAnchor); }
После чего нужно вызвать worksheetDrawing.Save(drawingsPart).
Если необходимо вставлять в файл Excel с картинкой или диаграммой (т.е. имеется /xl/drawings) разрывы страниц, то нужно в класс OXMLWorksheet_RU добавить код, который вставляет в worksheet rowBreaks перед drawing[2].
protected void saveBreaks() { DocumentFormat.OpenXml.Spreadsheet.Break pageBreak; SetEnumerator enumerator; int idx; DocumentFormat.OpenXml.Spreadsheet.Drawing drawing; //09.10.2017 if (this.rowBreaks().elements()) { enumerator = this.rowBreaks().getEnumerator(); //BP deviation documented rowBreaks = new DocumentFormat.OpenXml.Spreadsheet.RowBreaks(); //09.10.2017 --> drawing = OXMLExp::getFirstChildType(worksheet.get_ChildElements(), 'DocumentFormat.OpenXml.Spreadsheet.Drawing'); if (!drawing) { //09.10.2017 <-- OXML_RU::appendChild(worksheet, rowBreaks); //09.10.2017 --> } else { OXMLExp::insert(worksheet, rowBreaks, drawing, 'InsertBefore'); } //09.10.2017 <--
Код метода OXMLExp::getFirstChildType
public static System.Object getFirstChildType( System.Collections.IEnumerable _ie , str _type) { System.Object objectCompare, obj; System.Collections.IEnumerator ienum; System.Type type, childType; ; ienum = _ie.GetEnumerator(); type = CLRInterop::getType(_type); while (ienum.MoveNext()) { objectCompare = ienum.get_Current(); childType = objectCompare.GetType(); if (childType.Equals(type)) { return objectCompare; } } return obj; }
Метод OXMLExp::insert
public static System.Object insert( System.Object _parent , System.Object _child , System.Object _childRef , str _methodName ) { Array arrayT; System.Type type; System.Object child; arrayT = new Array(Types::Class); arrayT.value(1, _child); arrayT.value(2, _childRef); type = _child.GetType(); child = OXML_RU::invokeGeneric( _parent , _methodName , type , arrayT ); return child; }
Диаграмма Excel.
Для вставки диаграммы в стандартные шаблоны Excel, которые используются классами наследниками XMLExcelReport_RU, также необходимо создавать /xl/charts/styles и /xl/charts/colors, для создания которых в Open XML SDK нет классов, что видно в автоматически генерируемом коде утилитой OpenXMLSDKToolV25. Раздел формируется данными в виде строки. (ниже приведен код добавления xl/charts/colors)
ExtendedPart extendedPart1 = chartPart1.AddExtendedPart("http://schemas.microsoft.com/office/2011/relationships/chartColorStyle", "application/vnd.ms-office.chartcolorstyle+xml", "xml", "rId2"); GenerateExtendedPart1Content(extendedPart1);
// Generates content of part. private void GeneratePartContent(ExtendedPart part) { System.IO.Stream data = GetBinaryDataStream(partData); part.FeedData(data); data.Close(); } #region Binary Data private string partData = "PGNzOmNoYXJ0U3R5b……NoYXJ0U3R5bGU+"; private System.IO.Stream GetBinaryDataStream(string base64String) { return new System.IO.MemoryStream(System.Convert.FromBase64String(base64String)); }
Поэтому для использования диаграммы было принято решение создавать диаграмму в шаблоне, а потом в коде менять только диапазон данных и подписей. Т.к. диаграмма уже будет в шаблоне, то создавать новые drawingsPart и worksheetDrawing не нужно.
oxmlWorkBook_RU = document.workbook(); worksheetPart = oxmlWorkBook_RU.getWorksheet().worksheetPart(); worksheet = worksheetPart.get_Worksheet(); drawingsPart = worksheetPart.get_DrawingsPart(); worksheetDrawing = drawingsPart.get_WorksheetDrawing();
Метод изменения диапазонов диаграммы. Метод рассчитан для вызова один раз, например в методе createReport, также можно динамически рассчитывать внутри класса наследника XMLExcelReport_RU количество вставляемых записей и передавать их методу, и использовать вместо макроса #Rows.
#define.Rows(3) private void updateChartSeries() { DocumentFormat.OpenXml.Drawing.Charts.BarChartSeries barChartSeries; DocumentFormat.OpenXml.Drawing.Charts.CategoryAxisData categoryAxisData; DocumentFormat.OpenXml.Drawing.Charts.Formula formula; DocumentFormat.OpenXml.Drawing.Charts.Values values; DocumentFormat.OpenXml.Drawing.Charts.NumberReference numberReference; DocumentFormat.OpenXml.Drawing.Charts.BarChart barChart; DocumentFormat.OpenXml.Drawing.Charts.PlotArea plotArea; DocumentFormat.OpenXml.Drawing.Charts.Chart chart; DocumentFormat.OpenXml.Drawing.Charts.ChartSpace chartSpace; DocumentFormat.OpenXml.Packaging.ChartPart chartPart; System.Collections.IEnumerable ie; System.Collections.IEnumerator ienum; SetEnumerator setEnum; int curCol; container columnChar; ; columnChar = ['CZ', 'DA']; ie = drawingsPart.get_ChartParts(); ienum = ie.GetEnumerator(); ienum.MoveNext(); chartPart = ienum.get_Current(); chartSpace = chartPart.get_ChartSpace(); chart = OXMLExp::getFirstChildType(chartSpace.get_ChildElements(), 'DocumentFormat.OpenXml.Drawing.Charts.Chart'); plotArea = chart.get_PlotArea(); barChart = OXMLExp::getFirstChildType(plotArea.get_ChildElements(), 'DocumentFormat.OpenXml.Drawing.Charts.BarChart'); setEnum = OXMLExp::getSetEnumChildType(barChart.get_ChildElements(), 'DocumentFormat.OpenXml.Drawing.Charts.BarChartSeries'); curCol = 1; while (setEnum.moveNext()) { barChartSeries = setEnum.current(); categoryAxisData = OXMLExp::getFirstChildType(barChartSeries.get_ChildElements(), 'DocumentFormat.OpenXml.Drawing.Charts.CategoryAxisData'); numberReference = categoryAxisData.get_NumberReference(); formula = numberReference.get_Formula(); formula.set_Text(strFmt('Invoice4Paym!$DB$1:$DB$%1', #Rows)); values = OXMLExp::getFirstChildType(barChartSeries.get_ChildElements(), 'DocumentFormat.OpenXml.Drawing.Charts.Values'); numberReference = values.get_NumberReference(); formula = numberReference.get_Formula(); formula.set_Text(strFmt('Invoice4Paym!$%1$1:$%1$%2', conPeek(columnChar, curCol), #Rows)); curCol ++; } }
Метод OXMLExp::getSetEnumChildType
public static SetEnumerator getSetEnumChildType( System.Collections.IEnumerable _ie , str _type) { System.Object objectCompare; System.Collections.IEnumerator ienum; System.Type type, childType; Set set; ; ienum = _ie.GetEnumerator(); type = CLRInterop::getType(_type); set = new Set(Types::Class); while (ienum.MoveNext()) { objectCompare = ienum.get_Current(); childType = objectCompare.GetType(); if (childType.Equals(type)) { set.add(objectCompare); } } return set.getEnumerator(); }
Comments
So empty here ... leave a comment!