Generate Doc file using OPEN-XML SDK in UWP - c#

I want to Create a Microsoft Word Document in my UWP App and I've successfully created doc file in APP Local folder. But i want to create this doc file in a specific folder location chosen by user.
I mean user browse a location and then create doc file into that
location.
Here is my code to create Doc file in App local Path:
string docFileName = "TestDoc.docx";
Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
string Filepath = localFolder.Path + "\\" + docFileName;
using (var wordprocessingDocument = WordprocessingDocument.Create(file.Path, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
{
MainDocumentPart mainPart = wordprocessingDocument.AddMainDocumentPart();
mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
Body body = mainPart.Document.AppendChild(new Body());
DocumentFormat.OpenXml.Wordprocessing.Paragraph para = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
DocumentFormat.OpenXml.Wordprocessing.Run run = para.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Hello !!! ."));
wordprocessingDocument.MainDocumentPart.Document.Save();
}
I've got below error while trying to create doc file in a specific location like below:
Error
Message=The media is write protected : 'D:\Training\TestDoc.docx'
Code
string path = #"D:\Training\TestDoc.docx";
using (var wordprocessingDocument = WordprocessingDocument.Create(path, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
{
MainDocumentPart mainPart = wordprocessingDocument.AddMainDocumentPart();
mainPart.Document = new DocumentFormat.OpenXml.Wordprocessing.Document();
Body body = mainPart.Document.AppendChild(new Body());
DocumentFormat.OpenXml.Wordprocessing.Paragraph para = body.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Paragraph());
DocumentFormat.OpenXml.Wordprocessing.Run run = para.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Run());
run.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Text("Hello !!! ."));
wordprocessingDocument.MainDocumentPart.Document.Save();
}

Because UWP apps run in a sandbox, you cannot directly access filesystem paths using classic APIs. However, you can use Windows.Storage APIs to do so.
Let user choose the save file
If you want to let the user choose the save file, you can use the FileSavePicker API. This Docs page describes this in detail. Once done, the API will give you a StorageFile instance, which you can use.
Write in the file
As you cannot use the Create method which takes a file path as an argument, you need to use the Stream based one instead.
To get a Stream instance from the file, add using System.IO; to the top of your code file and then do:
using(var stream = await file.OpenStreamForWriteAsync())
With this stream you can then do:
using (var wordprocessingDocument =
WordprocessingDocument.Create(
stream, WordprocessingDocumentType.Document))
{
//... your code
}
Note: Broad filesystem access
If you really need to create the file at an arbitrary (not user selected) location, you can use the broadFileSystemAccess capability. This is however only for apps that really require it. Note, that even with this capability, you still need to perform all file operations using the StorageFile and StorageFolder APIs.

Related

Adding multiple images to a docx file using xslt template

So I need to generate a docx file for reporting purposes. This report contains text, tables and a lot of images.
So far, I managed to add text and a table (and populate it based on the content of my xml using an xslt transform).
However, I am stuck on adding images. I found some examples of how to add images using C# but I don't think this is what I need. I need to format the document using my xslt and add the images in the right places (for instance in a table cell). Is it somehow possible to add a container using xslt which uses the filepath to display/embed the image similar to the <img> tag in html?
I know that the docx format is basically a zip containing a file structure and to embed the image I should add it to this file structure also.
Any examples or references are appreciated.
to give you an idea of my code:
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(xsltFile);
StringWriter stringWriter = new StringWriter();
XmlWriter xmlWriter = XmlWriter.Create(stringWriter);
transform.Transform(xmlFile, xmlWriter);
XmlDocument newWordContent = new XmlDocument();
newWordContent.LoadXml(stringWriter.ToString());
File.Copy(docXtemplate, outputFilename, true);
using (WordprocessingDocument myDoc = WordprocessingDocument.Open(outputFilename, true))
{
MainDocumentPart mainPart = myDoc.MainDocumentPart;
Body body = new Body(newWordContent.DocumentElement.InnerXml);
DocumentFormat.OpenXml.Wordprocessing.Document document = new DocumentFormat.OpenXml.Wordprocessing.Document(body);
document.Save(mainPart);
}
It basically replaces the body of an existing docx file. This enables me to use all the formatting, etc.
The xslt file is generated by adjusting the document.xml file from the docx.
Update
Ok, so I figured out how to add an image to the docx file directory, see below
using (WordprocessingDocument myDoc = WordprocessingDocument.Open(outputFilename, true))
{
MainDocumentPart mainPart = myDoc.MainDocumentPart;
ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Png);
using (FileStream stream = new FileStream(imageFile, FileMode.Open))
{
imagePart.FeedData(stream);
}
Body body = new Body(newWordContent.DocumentElement.InnerXml);
DocumentFormat.OpenXml.Wordprocessing.Document document = new
DocumentFormat.OpenXml.Wordprocessing.Document(body);
document.Save(mainPart);
}
This will add the image to the docx structure. I also checkt the relatioship and this is present in the 'document.xml.rels' file. When I take this id and use it in my xslt to add the image to the document (for testing), I do see an area where the image should be when opening with Word, however it says: cannot display image with the red cross.
A difference I do notice is that image which where in the orignal docx are saved in "word\media" while the added image with the code above is added in "media". Not sure if this is a problem
Ok, So I think I figured it out.
XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(xsltFile);
StringWriter stringWriter = new StringWriter();
XmlWriter xmlWriter = XmlWriter.Create(stringWriter);
transform.Transform(xmlFile, xmlWriter);
XmlDocument newWordContent = new XmlDocument();
newWordContent.LoadXml(stringWriter.ToString());
using (WordprocessingDocument myDoc = WordprocessingDocument.Open(outputFilename, true))
{
MainDocumentPart mainPart = myDoc.MainDocumentPart;
ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Png, "imgId");
using (FileStream stream = new FileStream(imageFile, FileMode.Open))
{
imagePart.FeedData(stream);
}
Body body = new Body(newWordContent.DocumentElement.InnerXml);
DocumentFormat.OpenXml.Wordprocessing.Document document = new
DocumentFormat.OpenXml.Wordprocessing.Document(body);
document.Save(mainPart);
}
The above code will add an image to your docx file structure with a specific id. You can use this id to refer to in your xsl transform. In the code example from my question I didn't set the id but used the one that was generated. However, each time you run this code the image will be added to the file with a new id resulting in a "not able to display" error. Not one of my sharpest moments;-).
For my use case I have to add multiple images to a large document so that code will be different but I think that based on the above code this can be achieved.

Generic way to read all documents types programmatically

I am using the code below to read content of files from SharePoint 2019 On premise server. This code only work for word document though, Is there a generic code that will allow me to read other document type like PDF,PowerPoint,Excel etc
File file = clientContext.Web.GetFileByUrl(resultRow["Path"].ToString());
stream = file.OpenBinaryStream();
clientContext.Load(file);
clientContext.ExecuteQuery();
WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Open(stream.Value, false);
Body body = wordprocessingDocument.MainDocumentPart.Document.Body;
int bodylength = body.InnerText.Length;

Fill word template data using openXML SDK

I have an word file template and xml file for data. I want to find content Content control in word and get data from xml and then replace text in word template. i'm using the following code but it is not updating word file.
using (WordprocessingDocument document = WordprocessingDocument.CreateFromTemplate(txtWordFile.Text))
{
MainDocumentPart mainPart = document.MainDocumentPart;
IEnumerable<SdtBlock> block = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "TotalClose");
Text t = block.Descendants<Text>().Single();
t.Text = "13,450,542";
mainPart.Document.Save();
}
For anyone still struggling with this - you can check out this library https://github.com/antonmihaylov/OpenXmlTemplates
With it you can replace the text inside all content controls of the document based on a JSON object (or a basic C# dictionary) without writing specific code, instead you specify the variable name in the tag of the content control.
(Note - i am the maker of that library, but it is open sourced and licensed under LGPLv3)
I think you should write changes to temporary file.
See Save modified WordprocessingDocument to new file or my code from work project:
MemoryStream yourDocStream = new MemoryStream();
... // populate yourDocStream with .docx bytes
using (Package package = Package.Open(yourDocStream, FileMode.Open, FileAccess.ReadWrite))
{
// Load the document XML in the part into an XDocument instance.
PackagePart packagePart = LoadXmlPackagePart(package);
XDocument xDocument = XDocument.Load(XmlReader.Create(packagePart.GetStream()));
// making changes
// Save the XML into the package
using (XmlWriter xw = XmlWriter.Create(packagePart.GetStream(FileMode.Create, FileAccess.Write)))
{
xDocument.Save(xw);
}
var resultDocumentBytes = yourDocStream.ToArray();
}
The basic approach you use works fine, but I'm surprised you're not getting any compile-time errors because
IEnumerable<SdtBlock> block = mainPart.Document
.Body
.Descendants<SdtBlock>()
.Where(r => r.SdtProperties.GetFirstChild<Tag>().Val == "TotalClose");
is not compatible with
Text t = block.Descendants<Text>().Single();
block, as IEnumerable has no Descendants property. You either need to loop through all the items in IEnumerable and perform this on each item, or you need to define and instantiate a single item, like this:
using (WordprocessingDocument document = WordprocessingDocument.CreateFromTemplate(txtWordFile.Text))
{
MainDocumentPart mainPart = pkgDoc.MainDocumentPart;
SdtBlock block = mainPart.Document.Body.Descendants<SdtBlock>().Where
(r => r.SdtProperties.GetFirstChild<Tag>().Val == "test1").FirstOrDefault();
Text t = block.Descendants<Text>().Single();
t.Text = "13,450,542";
mainPart.Document.Save();
}

Generated file is still locked

I am using OpenXML SDK to generate and save a word document.
I am using a "Using" Block to create and dispose of the memory stream, and word document object when they are finished. However when trying to open to file i get the error that the file is still in use by another process.
Looking at Resource Monitor i have been able to find that it is my c# application still keeping it open.
When i close my application i can use the file
I have the following code.
private void button2_Click(object sender, EventArgs e)
{
// Create Stream
using (MemoryStream mem = new MemoryStream())
{
// Create Document
using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
{
// Add a main document part.
MainDocumentPart mainPart = wordDocument.AddMainDocumentPart();
// Create the document structure and add some text.
mainPart.Document = new Document();
Body docBody = new Body();
mainPart.Document.Append(docBody);
wordDocument.SaveAs(#"E:\Report\word.docx");
// Add your docx content here
wordDocument.Close();
}
}
}
Am i correct in understanding that
using (MemoryStream mem = new MemoryStream())
should dispose of the MemoryStream when the block finishes, and therefore allow the file to be used by another process?
Thanks
SaveAs returns a new package object that represents the package stored in that file. You need to Close that package too.
wordDocument.SaveAs(#"E:\Report\word.docx").Close();

Add (xml) file to a .xlsx file and keep after 'save as'

Hi I have a need to store some data in a excel file (.xlsx) so I can send the file to our customers and extract data from it again later when they send us the file back to update some content.
The customer should not be able to see the data we store in the file, and certainly not be able to remove it (accidentally). In fact he should not be aware of it in any way. Also we want to add this info from a service on a system that doesn't have Excel installed.
I know that a .xlsx file is basicaly a zip file so I can extract the data and add a file to it, zip it again and have a valid file that can be opened by Excel. Only problem here is that after saving that file in Excel my custom xml file is removed from the package. So I need to know how to fix this.
What I have:
XNamespace MyNamespace = "http://stackoverflow.com/questions/ask";
XNamespace ExcelNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
string ExtractionPath = #"C:\temp\test\";
string ExcelFile = #"C:\temp\example.xlsx";
Directory.CreateDirectory(ExtractionPath);
System.IO.Compression.ZipFile.ExtractToDirectory(ExcelFile, ExtractionPath);
var Root = new XElement(MyNamespace + "tracker",
new XAttribute("version", "1.0.0.1"),
new XElement("connections"));
var file = Path.Combine(ExtractionPath, "connectioninfo.xml");
if (!File.Exists(file))
{
var relsPath = Path.Combine(ExtractionPath, "_rels", ".rels");
var rels = XElement.Load(relsPath);
rels.Add(new XElement(ExcelNamespace + "Relationship",
new XAttribute("Id", "XXXX"),
new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml"),
new XAttribute("Target", "connectioninfo.xml")));
rels.Save(relsPath);
}
Root.Save(file);
if (File.Exists(ExcelFile)) File.Delete(ExcelFile);
System.IO.Compression.ZipFile.CreateFromDirectory(ExtractionPath, ExcelFile, System.IO.Compression.CompressionLevel.NoCompression, false);
When I run this code I end up with a file that contains my connectioninfo.xml file and that I can open in Excel. But when I save that file in excel and unzip the package again, then the connectioninfo.xml file is gone.
So question -> what am I missing to keep the file in the package after saving?
PS: I have also tried following code, but same problem ...
(using System.IO.Packaging;)
using (Package package = Package.Open(ExcelFile, FileMode.Open, FileAccess.ReadWrite))
{
Uri uriPartTarget = new Uri("/customXml/example.xml", UriKind.Relative);
if (!package.PartExists(uriPartTarget))
{
PackagePart customXml = package.CreatePart(uriPartTarget,
"application/vnd.openxmlformats-officedocument.customXmlProperties+xml");
using (Stream partStream = customXml.GetStream(FileMode.Create,
FileAccess.ReadWrite))
{
var doc = new XElement("test", new XElement("content","Hello world!"));
doc.Save(partStream);
}
}
}

Categories