How to edit pasted content using the Open XML SDK

445 Views Asked by At

I have a custom template in which I'd like to control (as best I can) the types of content that can exist in a document. To that end, I disable controls, and I also intercept pastes to remove some of those content types, e.g. charts. I am aware that this content can also be drag-and-dropped, so I also check for it later, but I'd prefer to stop or warn the user as soon as possible.

I have tried a few strategies:

  • RTF manipulation
  • Open XML manipulation

RTF manipulation is so far working fairly well, but I'd really prefer to use Open XML as I expect it to be more useful in the future. I just can't get it working.

Open XML Manipulation

The wonderfully-undocumented (as far as I can tell) "Embed Source" appears to contain a compound document object, which I can use to modify the copied content using the Open XML SDK. But I have been unable to put the modified content back into an object that lets it be pasted correctly.

The modification part seems to work fine. I can see, if I save the modified content to a temporary .docx file, that the changes are being made correctly. It's the return to the clipboard that seems to be giving me trouble.

I have tried assigning just the Embed Source object back to the clipboard (so that the other types such as RTF get wiped out), and in this case nothing at all gets pasted. I've also tried re-assigning the Embed Source object back to the clipboard's data object, so that the remaining data types are still there (but with mismatched content, probably), which results in an empty embedded document getting pasted.

Here's a sample of what I'm doing with Open XML:

using OpenMcdf;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

...

object dataObj = Forms.Clipboard.GetDataObject();
object embedSrcObj = dateObj.GetData("Embed Source");
if (embedSrcObj is Stream)
{
  // read it with OpenMCDF

  Stream stream = embedSrcObj as Stream;
  CompoundFile cf = new CompoundFile(stream);
  CFStream cfs = cf.RootStorage.GetStream("package");
  byte[] bytes = cfs.GetData();
  string savedDoc = Path.GetTempFileName() + ".docx";
  File.WriteAllBytes(savedDoc, bytes);

  // And then use the OpenXML SDK to read/edit the document:

  using (WordprocessingDocument openDoc = WordprocessingDocument.Open(savedDoc, true))
  {
    OpenXmlElement body = openDoc.MainDocumentPart.RootElement.ChildElements[0];
    foreach (OpenXmlElement ele in body.ChildElements)
    {
      if (ele is Paragraph)
      {
        Paragraph para = (Paragraph)ele;
        if (para.ParagraphProperties != null && para.ParagraphProperties.ParagraphStyleId != null)
        {
          string styleName = para.ParagraphProperties.ParagraphStyleId.Val;

          Run run = para.LastChild as Run; // I know I'm assuming things here but it's sufficient for a test case
          run.RunProperties = new RunProperties();
          run.RunProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("test"));
        }
      }
      // etc.
    }

    openDoc.MainDocumentPart.Document.Save(); // I think this is redundant in later versions than what I'm using
  }

  // repackage the document

  bytes = File.ReadAllBytes(savedDoc);
  cf.RootStorage.Delete("Package");
  cfs = cf.RootStorage.AddStream("Package");
  cfs.Append(bytes);

  MemoryStream ms = new MemoryStream();
  cf.Save(ms);
  ms.Position = 0;

  dataObj.SetData("Embed Source", ms);
  // or,
  // Clipboard.SetData("Embed Source", ms);
}

Question

What am I doing wrong? Is this just a bad/unworkable approach?

0

There are 0 best solutions below