Как я могу встроить любой тип файла в Microsoft Word, используя OpenXml 2.0 - PullRequest
8 голосов
/ 24 июля 2010

Я потратил много времени, пытаясь найти хороший способ встроить любой файл в Microsoft Word, используя OpenXml 2.0; Документы Office довольно просты, но как насчет других типов файлов, таких как PDF, TXT, GIF, JPG, HTML и т. Д ....

Какой хороший способ заставить это работать для любого типа файла, в C #?

Ответы [ 4 ]

19 голосов
/ 24 июля 2010

Встраивание посторонних объектов (PDF, TXT, GIF и т. Д.) В Microsoft Word с использованием OpenXml 2.0 (Ну, в сотрудничестве с COM)

Я многое получил с этого сайта, поэтому здесь я спросил и ответил на свой вопрос, чтобы немного рассказать о теме, по которой у меня возникли трудности с поиском ответов, надеюсь, что это поможет людям.

Существует несколько примеров, показывающих, как встраивать документ Office в другой документ Office с использованием OpenXml 2.0, чего нет и легко понять, как встроить практически любой файл в документ Office.

Я многому научился из кода других людей, так что это моя попытка внести свой вклад. Поскольку я уже использую OpenXml для генерации документов и мне нужно встраивать другие файлы в Word, я решил использовать совместную работу OpenXml и COM (Microsoft Office 2007 dll's) для достижения моей цели. Если вы похожи на меня, «вызов приложения OLE-сервера для создания IStorage» не имеет большого значения для вас.

В этом примере я хотел бы показать, как я использую COM для ПРОГРАММНОГО получения информации о двоичных данных OLE вложенного файла, а затем как я использовал эту информацию в своем документе OpenXml. По сути, я программно смотрю на отражатель документов OpenXml 2.0, чтобы получить необходимую информацию.

Мой код ниже разбит на несколько классов, но вот краткое описание того, что я делаю:

  1. Создайте OpenXml WordProcessingDocument, получите System.IO.FileInfo для файла, который вы хотите встроить
  2. Создание пользовательского объекта OpenXmlEmbeddedObject (это то, что содержит все двоичные данные)
  3. Используйте двоичные данные из вышеприведенного шага для создания потоков данных и изображений
  4. Используйте эти потоки как объект файла и изображение файла для документа OpenXml

Я знаю, что много кода и мало объяснений ... Надеюсь, за ним легко следовать и оно поможет людям 

Требования: • DocumentFormat.OpenXml dll (OpenXml 2.0) • WindowsBase DLL • Microsoft.Office.Interop.Word dll (Office 2007 - версия 12)

• Это основной класс, который запускает все, открывает WordProcessingDocument и класс для прикрепления файла

using DocumentFormat.OpenXml.Packaging;
using System.IO;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Wordprocessing;

public class MyReport
{
    private MainDocumentPart _mainDocumentPart;

    public void CreateReport()
    {
        using (WordprocessingDocument wpDocument = WordprocessingDocument.Create(@"TempPath\MyReport.docx", WordprocessingDocumentType.Document))
        {
            _mainDocumentPart = wpDocument.AddMainDocumentPart();
            _mainDocumentPart.Document = new Document(new Body());

            AttachFile(@"MyFilePath\MyFile.pdf", true);
        }
    }

    private void AttachFile(string filePathAndName, bool displayAsIcon)
    {
        FileInfo fileInfo = new FileInfo(filePathAndName);

        OpenXmlHelper.AppendEmbeddedObject(_mainDocumentPart, fileInfo, displayAsIcon);
    }
}

• Этот класс в классе помощника OpenXml содержит всю логику для встраивания объекта в файл OpenXml

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Validation;
using DocumentFormat.OpenXml.Wordprocessing;
using OVML = DocumentFormat.OpenXml.Vml.Office;
using V = DocumentFormat.OpenXml.Vml;

public class OpenXmlHelper
{
    /// <summary>
    /// Appends an Embedded Object into the specified Main Document
    /// </summary>
    /// <param name="mainDocumentPart">The MainDocument Part of your OpenXml Word Doc</param>
    /// <param name="fileInfo">The FileInfo object associated with the file being embedded</param>
    /// <param name="displayAsIcon">Whether or not to display the embedded file as an Icon (Otherwise it will display a snapshot of the file)</param>
    public static void AppendEmbeddedObject(MainDocumentPart mainDocumentPart, FileInfo fileInfo, bool displayAsIcon)
    {
        OpenXmlEmbeddedObject openXmlEmbeddedObject = new OpenXmlEmbeddedObject(fileInfo, displayAsIcon);

        if (!String.IsNullOrEmpty(openXmlEmbeddedObject.OleObjectBinaryData))
        {
            using (Stream dataStream = new MemoryStream(Convert.FromBase64String(openXmlEmbeddedObject.OleObjectBinaryData)))
            {
                if (!String.IsNullOrEmpty(openXmlEmbeddedObject.OleImageBinaryData))
                {
                    using (Stream emfStream = new MemoryStream(Convert.FromBase64String(openXmlEmbeddedObject.OleImageBinaryData)))
                    {
                        string imagePartId = GetUniqueXmlItemID();
                        ImagePart imagePart = mainDocumentPart.AddImagePart(ImagePartType.Emf, imagePartId);

                        if (emfStream != null)
                        {
                            imagePart.FeedData(emfStream);
                        }

                        string embeddedPackagePartId = GetUniqueXmlItemID();

                        if (dataStream != null)
                        {
                            if (openXmlEmbeddedObject.ObjectIsOfficeDocument)
                            {
                                EmbeddedPackagePart embeddedObjectPart = mainDocumentPart.AddNewPart<EmbeddedPackagePart>(
                                    openXmlEmbeddedObject.FileContentType, embeddedPackagePartId);
                                embeddedObjectPart.FeedData(dataStream);
                            }
                            else
                            {
                                EmbeddedObjectPart embeddedObjectPart = mainDocumentPart.AddNewPart<EmbeddedObjectPart>(
                                    openXmlEmbeddedObject.FileContentType, embeddedPackagePartId);
                                embeddedObjectPart.FeedData(dataStream);
                            }
                        }

                        if (!displayAsIcon && !openXmlEmbeddedObject.ObjectIsPicture)
                        {
                            Paragraph attachmentHeader = CreateParagraph(String.Format("Attachment: {0} (Double-Click to Open)", fileInfo.Name));
                            mainDocumentPart.Document.Body.Append(attachmentHeader);
                        }

                        Paragraph embeddedObjectParagraph = GetEmbeededObjectParagraph(openXmlEmbeddedObject.FileType,
                            imagePartId, openXmlEmbeddedObject.OleImageStyle, embeddedPackagePartId);

                        mainDocumentPart.Document.Body.Append(embeddedObjectParagraph);
                    }
                }
            }
        }
    }

    /// <summary>
    /// Gets Paragraph that includes the embedded object
    /// </summary>
    private static Paragraph GetEmbeededObjectParagraph(string fileType, string imageID, string imageStyle, string embeddedPackageID)
    {
        EmbeddedObject embeddedObject = new EmbeddedObject();

        string shapeID = GetUniqueXmlItemID();
        V.Shape shape = new V.Shape() { Id = shapeID, Style = imageStyle };
        V.ImageData imageData = new V.ImageData() { Title = "", RelationshipId = imageID };

        shape.Append(imageData);
        OVML.OleObject oleObject = new OVML.OleObject()
        {
            Type = OVML.OleValues.Embed,
            ProgId = fileType,
            ShapeId = shapeID,
            DrawAspect = OVML.OleDrawAspectValues.Icon,
            ObjectId = GetUniqueXmlItemID(),
            Id = embeddedPackageID
        };

        embeddedObject.Append(shape);
        embeddedObject.Append(oleObject);

        Paragraph paragraphImage = new Paragraph();

        Run runImage = new Run(embeddedObject);
        paragraphImage.Append(runImage);

        return paragraphImage;
    }

    /// <summary>
    /// Gets a Unique ID for an XML Item, for reference purposes
    /// </summary>
    /// <returns>A GUID string with removed dashes</returns>
    public static string GetUniqueXmlItemID()
    {
        return "r" + System.Guid.NewGuid().ToString().Replace("-", "");
    }

    private static Paragraph CreateParagraph(string paragraphText)
    {
        Paragraph paragraph = new Paragraph();
        ParagraphProperties paragraphProperties = new ParagraphProperties();

        paragraphProperties.Append(new Justification()
        {
            Val = JustificationValues.Left
        });

        paragraphProperties.Append(new SpacingBetweenLines()
        {
            After = Convert.ToString(100),
            Line = Convert.ToString(100),
            LineRule = LineSpacingRuleValues.AtLeast
        });

        Run run = new Run();
        RunProperties runProperties = new RunProperties();

        Text text = new Text();

        if (!String.IsNullOrEmpty(paragraphText))
        {
            text.Text = paragraphText;
        }

        run.Append(runProperties);
        run.Append(text);

        paragraph.Append(paragraphProperties);
        paragraph.Append(run);

        return paragraph;
    }

}

• Это самая важная часть этого процесса, она использует внутренний OLE-сервер Microsoft, создает двоичные данные DATA и двоичные данные EMF для файла. Все, что вам нужно здесь, это вызвать конструктор OpenXmlEmbeddedObject и обо всем позаботиться. Он будет имитировать процесс, который происходит, когда вы вручную перетаскиваете любой файл в Word; когда вы делаете это, происходит какое-то преобразование, превращающее файл в объект OLE, чтобы Microsoft могла его распознать. o Наиболее важными частями этого класса являются свойства OleObjectBinaryData и OleImageBinaryData; они содержат двоичную информацию о 64-битной строке для данных файла и изображение «.emf». o Если вы решите не отображать файл в виде значка, то данные изображения «.emf» создадут моментальный снимок файла, например первую страницу файла PDF, в которой вы все равно можете дважды щелкнуть, чтобы открыть o Если вы встраиваете изображение и решаете не отображать его как значок, свойства OleObjectBinaryData и OleImageBinaryData будут одинаковыми

using System.Runtime.InteropServices;
using System.Xml;
using System.Diagnostics;
using System.IO;
using System.Drawing;
using Microsoft.Office.Interop.Word;

public class OpenXmlEmbeddedObject
{
    #region Constants

    private const string _defaultOleContentType = "application/vnd.openxmlformats-officedocument.oleObject";
    private const string _oleObjectDataTag = "application/vnd";
    private const string _oleImageDataTag = "image/x-emf";

    #endregion Constants

    #region Member Variables

    private static FileInfo _fileInfo;
    private static string _filePathAndName;
    private static bool _displayAsIcon;
    private static bool _objectIsPicture;

    private object _objectMissing = System.Reflection.Missing.Value;
    private object _objectFalse = false;
    private object _objectTrue = true;

    #endregion Member Variables

    #region Properties

    /// <summary>
    /// The File Type, as stored in Registry (Ex: a GIF Image = 'giffile')
    /// </summary>
    public string FileType
    {
        get
        {
            if (String.IsNullOrEmpty(_fileType) && _fileInfo != null)
            {
                _fileType = GetFileType(_fileInfo, false);
            }

            return _fileType;
        }
    }
    private string _fileType;

    /// <summary>
    /// The File Context Type, as storered in Registry (Ex: a GIF Image = 'image/gif')
    /// * Is converted into the 'Default Office Context Type' for non-office files
    /// </summary>
    public string FileContentType
    {
        get
        {
            if (String.IsNullOrEmpty(_fileContentType) && _fileInfo != null)
            {
                _fileContentType = GetFileContentType(_fileInfo);

                if (!_fileContentType.Contains("officedocument"))
                {
                    _fileContentType = _defaultOleContentType;
                }
            }

            return _fileContentType;
        }
    }
    private string _fileContentType;

    /// <summary>
    /// Gets the ContentType Text for the file
    /// </summary>
    public static string GetFileContentType(FileInfo fileInfo)
    {
        if (fileInfo == null)
        {
            throw new ArgumentNullException("fileInfo");
        }

        string mime = "application/octetstream";

        string ext = System.IO.Path.GetExtension(fileInfo.Name).ToLower(); 

        Microsoft.Win32.RegistryKey rk = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);

        if (rk != null && rk.GetValue("Content Type") != null)
        {
            mime = rk.GetValue("Content Type").ToString();
        }

        return mime;
    }

    public bool ObjectIsOfficeDocument
    {
        get { return FileContentType != _defaultOleContentType; }
    }

    public bool ObjectIsPicture
    {
        get { return _objectIsPicture; }
    }

    public string OleObjectBinaryData
    {
        get { return _oleObjectBinaryData; }
        set { _oleObjectBinaryData = value; }
    }
    private string _oleObjectBinaryData;

    public string OleImageBinaryData
    {
        get { return _oleImageBinaryData; }
        set { _oleImageBinaryData = value; }
    }
    private string _oleImageBinaryData;

    /// <summary>
    /// The OpenXml information for the Word Application that is created (Make-Shoft Code Reflector)
    /// </summary>
    public string WordOpenXml
    {
        get { return _wordOpenXml; }
        set { _wordOpenXml = value; }
    }
    private String _wordOpenXml;

    /// <summary>
    /// The XmlDocument that is created based on the OpenXml Data from WordOpenXml
    /// </summary>
    public XmlDocument OpenXmlDocument
    {
        get
        {
            if (_openXmlDocument == null && !String.IsNullOrEmpty(WordOpenXml))
            {
                _openXmlDocument = new XmlDocument();
                _openXmlDocument.LoadXml(WordOpenXml);
            }

            return _openXmlDocument;
        }
    }
    private XmlDocument _openXmlDocument;

    /// <summary>
    /// The XmlNodeList, for all Nodes containing 'binaryData'
    /// </summary>
    public XmlNodeList BinaryDataXmlNodesList
    {
        get
        {
            if (_binaryDataXmlNodesList == null && OpenXmlDocument != null)
            {
                _binaryDataXmlNodesList = OpenXmlDocument.GetElementsByTagName("pkg:binaryData");
            }

            return _binaryDataXmlNodesList;
        }
    }
    private XmlNodeList _binaryDataXmlNodesList;

    /// <summary>
    /// Icon Object for the file
    /// </summary>
    public Icon ObjectIcon
    {
        get
        {
            if (_objectIcon == null)
            {
                _objectIcon = Enterprise.Windows.Win32.Win32.GetLargeIcon(_filePathAndName);
            }

            return _objectIcon;
        }
    }
    private Icon _objectIcon;

    /// <summary>
    /// File Name for the Icon being created
    /// </summary>
    public string ObjectIconFile
    {
        get
        {
            if (String.IsNullOrEmpty(_objectIconFile))
            {
                _objectIconFile = String.Format("{0}.ico", _filePathAndName.Replace(".", ""));
            }

            return _objectIconFile;
        }
    }
    private string _objectIconFile;

    /// <summary>
    /// Gets the original height and width of the emf file being created
    /// </summary>
    public string OleImageStyle
    {
        get
        {
            if (String.IsNullOrEmpty(_oleImageStyle) && !String.IsNullOrEmpty(WordOpenXml))
            {
                XmlNodeList xmlNodeList = OpenXmlDocument.GetElementsByTagName("v:shape");
                if (xmlNodeList != null && xmlNodeList.Count > 0)
                {
                    foreach (XmlAttribute attribute in xmlNodeList[0].Attributes)
                    {
                        if (attribute.Name == "style")
                        {
                            _oleImageStyle = attribute.Value;
                        }
                    }
                }
            }

            return _oleImageStyle;
        }

        set { _oleImageStyle = value; }
    }
    private string _oleImageStyle;

    #endregion Properties

    #region Constructor

    /// <summary>
    /// Generates binary information for the file being passed in
    /// </summary>
    /// <param name="fileInfo">The FileInfo object for the file to be embedded</param>
    /// <param name="displayAsIcon">Whether or not to display the file as an Icon (Otherwise it will show a snapshot view of the file)</param>
    public OpenXmlEmbeddedObject(FileInfo fileInfo, bool displayAsIcon)
    {
        _fileInfo = fileInfo;
        _filePathAndName = fileInfo.ToString();
        _displayAsIcon = displayAsIcon;

        SetupOleFileInformation();
    }

    #endregion Constructor

    #region Methods

    /// <summary>
    /// Creates a temporary Word App in order to add an OLE Object, get's the OpenXML data from the file (similar to the Code Reflector info)
    /// </summary>
    private void SetupOleFileInformation()
    {
        Microsoft.Office.Interop.Word.Application wordApplication = new Microsoft.Office.Interop.Word.Application();

        Microsoft.Office.Interop.Word.Document wordDocument = wordApplication.Documents.Add(ref _objectMissing, ref _objectMissing,
            ref _objectMissing, ref _objectMissing);

        object iconObjectFileName = _objectMissing;
        object objectClassType = FileType;
        object objectFilename = _fileInfo.ToString();

        Microsoft.Office.Interop.Word.InlineShape inlineShape = null;

        if (_displayAsIcon)
        {
            if (ObjectIcon != null)
            {
                using (FileStream iconStream = new FileStream(ObjectIconFile, FileMode.Create))
                {
                    ObjectIcon.Save(iconStream);
                    iconObjectFileName = ObjectIconFile;
                }
            }

            object objectIconLabel = _fileInfo.Name;

            inlineShape = wordDocument.InlineShapes.AddOLEObject(ref objectClassType,
                ref objectFilename, ref _objectFalse, ref _objectTrue, ref iconObjectFileName,
                ref _objectMissing, ref objectIconLabel, ref _objectMissing);
        }
        else
        {
            try
            {
                Image image = Image.FromFile(_fileInfo.ToString());
                _objectIsPicture = true;
                OleImageStyle = String.Format("height:{0}pt;width:{1}pt", image.Height, image.Width);

                wordDocument.InlineShapes.AddPicture(_fileInfo.ToString(), ref _objectMissing, ref _objectTrue, ref _objectMissing);
            }
            catch
            {
                inlineShape = wordDocument.InlineShapes.AddOLEObject(ref objectClassType,
                    ref objectFilename, ref _objectFalse, ref _objectFalse, ref _objectMissing, ref _objectMissing,
                    ref _objectMissing, ref _objectMissing);
            }
        }

        WordOpenXml = wordDocument.Range(ref _objectMissing, ref _objectMissing).WordOpenXML;

        if (_objectIsPicture)
        {
            OleObjectBinaryData = GetPictureBinaryData();
            OleImageBinaryData = GetPictureBinaryData();
        }
        else
        {
            OleObjectBinaryData = GetOleBinaryData(_oleObjectDataTag);
            OleImageBinaryData = GetOleBinaryData(_oleImageDataTag);
        }

        // Not sure why, but Excel seems to hang in the processes if you attach an Excel file…
        // This kills the excel process that has been started < 15 seconds ago (so not to kill the user's other Excel processes that may be open)
        if (FileType.StartsWith("Excel"))
        {
            Process[] processes = Process.GetProcessesByName("EXCEL");
            foreach (Process process in processes)
            {
                if (DateTime.Now.Subtract(process.StartTime).Seconds <= 15)
                {
                    process.Kill();
                    break;
                }
            }
        }

        wordDocument.Close(ref _objectFalse, ref _objectMissing, ref _objectMissing);
        wordApplication.Quit(ref _objectMissing, ref _objectMissing, ref _objectMissing);
    }

    /// <summary>
    /// Gets the binary data from the Xml File that is associated with the Tag passed in
    /// </summary>
    /// <param name="binaryDataXmlTag">the Tag to look for in the OpenXml</param>
    /// <returns></returns>
    private string GetOleBinaryData(string binaryDataXmlTag)
    {
        string binaryData = null;
        if (BinaryDataXmlNodesList != null)
        {
            foreach (XmlNode xmlNode in BinaryDataXmlNodesList)
            {
                if (xmlNode.ParentNode != null)
                {
                    foreach (XmlAttribute attr in xmlNode.ParentNode.Attributes)
                    {
                        if (String.IsNullOrEmpty(binaryData) && attr.Value.Contains(binaryDataXmlTag))
                        {
                            binaryData = xmlNode.InnerText;
                            break;
                        }
                    }
                }
            }
        }

        return binaryData;
    }

    /// <summary>
    /// Gets the image Binary data, if the file is an image
    /// </summary>
    /// <returns></returns>
    private string GetPictureBinaryData()
    {
        string binaryData = null;
        if (BinaryDataXmlNodesList != null)
        {
            foreach (XmlNode xmlNode in BinaryDataXmlNodesList)
            {
                binaryData = xmlNode.InnerText;
                break;
            }
        }

        return binaryData;
    }

    /// <summary>
    /// Gets the file type description ("Application", "Text Document", etc.) for the file.
    /// </summary>
    /// <param name="fileInfo">FileInfo containing extention</param>
    /// <returns>Type Description</returns>
    public static string GetFileType(FileInfo fileInfo, bool returnDescription)
    {
        if (fileInfo == null)
        {
            throw new ArgumentNullException("fileInfo");
        }

        string description = "File";
        if (string.IsNullOrEmpty(fileInfo.Extension))
        {
            return description;
        }
        description = string.Format("{0} File", fileInfo.Extension.Substring(1).ToUpper());
        RegistryKey typeKey = Registry.ClassesRoot.OpenSubKey(fileInfo.Extension);
        if (typeKey == null)
        {
            return description;
        }
        string type = Convert.ToString(typeKey.GetValue(string.Empty));
        RegistryKey key = Registry.ClassesRoot.OpenSubKey(type);
        if (key == null)
        {
            return description;
        }

        if (returnDescription)
        {
            description = Convert.ToString(key.GetValue(string.Empty));
            return description;
        }
        else
        {
            return type;
        }
    }

    #endregion Methods
}
0 голосов
/ 19 февраля 2018

Это отличный ответ, и он мне очень помог, но потенциальная ошибка, о которой упоминал пользователь bic, также существует

в

OpenXmlEmbeddedObject(FileInfo fileInfo, bool displayAsIcon)

в строке 242,

_filePathAndName = fileInfo.ToString();

в

SetupOleFileInformation()

в строке 264,

 object objectFilename = _fileInfo.ToString();

в строке 289 и

Image image = Image.FromFile(_fileInfo.ToString());

в строке 293

wordDocument.InlineShapes.AddPicture(_fileInfo.toString(), ref _objectMissing, ref _objectTrue, ref _objectMissing);

Все они должны быть «FullName» вместо «ToString ()», если код должен работать и с относительными путями.Надеюсь, это поможет любому, кто хочет использовать код D Lyonnais!

0 голосов
/ 22 июля 2016
 _objectIcon = Enterprise.Windows.Win32.Win32.GetLargeIcon(_filePathAndName); 

кажется сломанным, но

_objectIcon = System.Drawing.Icon.ExtractAssociatedIcon(_filePathAndName);

также должно работать.

0 голосов
/ 24 июля 2010

Мой ответ здесь будет сообщать вам, как это сделать, но не покажет вам SDK или определенный язык.

...