4
votes

I'm having a nightmare of a time trying to add a Chart to a MemoryStream in-memory.

I'm creating a Word document on the fly using OpenXML and I have a chart that is also being dynamically generated from data in the database.

I get the template from the database as a byte array, passing that into a method that also takes a business object that holds a bunch of data to populate bookmarks held within that template.

Here's the method:

public Stream Parse(byte[] array, AudiometryReport AudReport)
{

    using (MemoryStream Stream = new MemoryStream())
    {
        Stream.Write(array, 0, (int)array.Length);
        Stream.Position = 0;

        using (document = WordprocessingDocument.Open(Stream, true))
        {
            XDocument doc = document.MainDocumentPart.GetXDocument();

            List<XElement> bookmarks = doc.Descendants()
                .Where(n => n.NodeType == XmlNodeType.Element && n.Name.LocalName == "bookmarkStart")
                .ToList();

            PropertyInfo[] reportInfo = AudReport.GetType().GetProperties();

            foreach (XElement bm in bookmarks)
            {
                try
                {

                    if (bm.LastAttribute.Value == "AudiometryChart")
                    {
                        string partId = InsertImage(document.MainDocumentPart);

                        var element = AddImageToDocument(document.MainDocumentPart, partId);
                        //var element = InsertImageXElement(partId);

                        //bm.ReplaceWith(new XElement(w + "r", element));
                    }
                    else
                    {
                        string val = reportInfo.Single(x => x.Name == bm.LastAttribute.Value).GetValue(AudReport, null).ToString();

                        bm.ReplaceWith(new XElement(w + "r",
                                new XElement(w + "t", val)));
                    }
                }
                catch
                { }
            }

            document.MainDocumentPart.PutXDocument();
            //foreach (BookmarkStart bm in (IEnumerable<BookmarkStart>)document.MainDocumentPart.Document.Descendants<BookmarkStart>())
            //{
            //    if (bm.Name == "AudiometryChart")
            //    {
            //        // Insert the chart object here.
            //        //AddImage(document);
            //    }
            //    populateStaffDetails(AudReport.Report.Employee, bm);
            //    populateAudiometryDetails(AudReport, bm);
            //}

        }


        MemoryStream s = new MemoryStream();
        Stream.WriteTo(s);
        s.Position = 0;
        return s;
    }

}

The InsertImage image takes the MainDocumentPart and attaches a new ImagePart from the image I stream from the database. I pass the ID of that part back to the calling method.

private string InsertImage(MainDocumentPart docPart)
{
    //DrawingsPart dp = docPart.AddNewPart<DrawingsPart>();
    //ImagePart part = dp.AddImagePart(ImagePartType.Png, docPart.GetIdOfPart(dp));
    ImagePart part = docPart.AddImagePart(ImagePartType.Png);
    Chart cht = new ChartBuilder().DoChart(Data, new string[] { "Left", "Right", "Normal" });

    using (MemoryStream ms = new MemoryStream())
    {
        cht.SaveImage(ms, ChartImageFormat.Png);
        ms.Position = 0;

        part.FeedData(ms);
    }

    //int count = dp.ImageParts.Count<ImagePart>();
    int count = docPart.ImageParts.Count<ImagePart>();


    return docPart.GetIdOfPart(part);
}

The last part is some serious nastiness that is allegdly required to add one image to one word document, but what the hell - here it is anyway:

private Run AddImageToDocument(MainDocumentPart docPart, string ImageRelId)
{
    string ImageFileName = "Audiometry Chart Example";
    string GraphicDataUri = "http://schemas.openxmlformats.org/drawingml/2006/picture";

    long imageLength = 990000L;
    long imageHeight = 792000L;

    var run = new Run(
        new Drawing(
            new wp.Inline(

                new wp.Extent() { Cx = imageLength, Cy = imageHeight },

                new wp.EffectExtent()
                {
                    LeftEdge = 19050L,
                    TopEdge = 0L,
                    RightEdge = 9525L,
                    BottomEdge = 0L
                },

                new wp.DocProperties()
                {
                    Id = (UInt32Value)1U,
                    Name = "Inline Text Wrapping Picture",
                    Description = ImageFileName
                },

                new wp.NonVisualGraphicFrameDrawingProperties(
                    new a.GraphicFrameLocks() { NoChangeAspect = true }),

                new a.Graphic(
                    new a.GraphicData(
                        new pic.Picture(

                            new pic.NonVisualPictureProperties(
                                new pic.NonVisualDrawingProperties() { Id = (UInt32Value)0U, Name = ImageFileName },
                                new pic.NonVisualPictureDrawingProperties()),

                            new pic.BlipFill(
                                new a.Blip() { Embed = ImageRelId },
                                new a.Stretch(
                                    new a.FillRectangle())),

                            new pic.ShapeProperties(
                                new a.Transform2D(
                                    new a.Offset() { X = 0L, Y = 0L },
                                    new a.Extents() { Cx = imageLength, Cy = imageHeight }),

                                new a.PresetGeometry(
                                    new a.AdjustValueList()) { Preset = a.ShapeTypeValues.Rectangle }))

                        ) { Uri = GraphicDataUri }))
                {
                    DistanceFromTop = (UInt32Value)0U,
                    DistanceFromBottom = (UInt32Value)0U,
                    DistanceFromLeft = (UInt32Value)0U,
                    DistanceFromRight = (UInt32Value)0U
                }
            ));

    return run;
}

So I've solved issues where the memory stream was causing problems by closing prematurely and probably a dozen other unnecessary amateur garden path problems but that image will just not show up in my document. Frustrating. Suggestions or divine inspiration very welcome right now.

(this question has been heavily edited so some answers may not relate to the wording of this question).

2

2 Answers

1
votes

I've just tested your AddImageToDocument function in a small test scenario using the following code:

string partId = ...

Run element = AddImageToDocument(newdoc.MainDocumentPart, partId);

Paragraph p = new Paragraph() { RsidParagraphAddition = "00EA6221", RsidRunAdditionDefault = "008D25CC" };

p.AppendChild(element);

newdoc.MainDocumentPart.Document.Body.Append(p);

// Save the word document here...

Everything works as expected and the image shows up in the word document.

Then I've come to the conclusion that the problem in your code must be the replacement of the bookmarkStart tag and the conversion of the Run (containing the image) to an XElement.

So, I've modified your code in the following way (using XElement.Parse to convert an OpenXmlElement to a XElement):

foreach (XElement bm in bookmarks)
{
  try
  {
    if (bm.LastAttribute.Value == "AudiometryChart")
    {
      string partId = InsertImage(document.MainDocumentPart);

      Run element = AddImageToDocument(document.MainDocumentPart, partId);

      bm.ReplaceWith(XElement.Parse(element.OuterXml)); // Use XElement.Parse to convert an OpenXmlElement to an XElement.
    }
    else
    {
      ...                    }
    }
  }
  catch
  {
  }
}

The image now shows up in the word document.

Then I've analyzed the word document using the OpenXml SDK productivity tool and found that the bookmarkEnd tags still exist in the document.

To remove those tags use the following code:

List<XElement> bookmarksEnd = doc.Descendants()
            .Where(n => n.NodeType == XmlNodeType.Element && n.Name.LocalName == "bookmarkEnd")
            .ToList();

foreach (XElement x in bookmarksEnd)
{
  x.Remove();  
}     
1
votes

Edit 3: :o) Ok, I found the problem. If you initialize the document's MemoryStream with the doc content, the buffer will be fixed in size and not editable. Just changed the init to write the doc content after creation and all seemd to work fine.

//using (MemoryStream stream = new MemoryStream (docxFile))
using (MemoryStream stream = new MemoryStream ())
{
    stream.Write (docxFile, 0, docxFile.Length);
    stream.Position = 0;
    using (WordprocessingDocument docx = WordprocessingDocument.Open (stream, true))
    {
         [....]

Cheers