Пример изображения и диаграммы в 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();
}

Источники

  1. How to insert an image in Excel Open XML
  2. Excel Open XML RowBreaks and Footer Images — XML file positioning

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar