Generated file is still locked - c#

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();

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.

MS Word - Default save as type is web page

I am generating a word document programmatically using c# language. The document is generated properly in .doc format, but when I try to use save as option for this document, it shows web page options as default selected option. I want .doc or .docx option as default save as type for this document. In other documents it is showing .doc as default save as type which is proper.
I am generating the document using below code:
var loMemoryStream = new MemoryStream();
using (var loFileStream = new FileStream(lsHtmlFileFullPath, FileMode.Open))
{
await loFileStream.CopyToAsync(loMemoryStream);
}
loMemoryStream.Position = 0;
FileStreamResult loInputFileStream = File(loMemoryStream, "application/vnd.ms-word", Path.GetFileName(lsDocFileFullPath));
using (System.IO.FileStream loOutputFileStream = new System.IO.FileStream(lsDocFileFullPath, FileMode.Create))
{
loInputFileStream.FileStream.Seek(0, SeekOrigin.Begin);
loInputFileStream.FileStream.CopyTo(loOutputFileStream);
loInputFileStream.FileStream.Seek(0, SeekOrigin.Begin);
}
How can I get .doc or .docx file format as default save as type programmatically?
Thank you in advance!

Filling out XFA PDF using iTextSharp, data not saving (C#)

I have an XFA PDF file (which I did not author). It's a third-party form which I'm trying to fill out. I filled out the form manually, then I used iTextSharp save the full XML DomDocument from it. Now I'm trying to apply that same XML file programmatically. However, the resulting PDF doesn't have any of the fields filled in. This is the code I'm using to apply the XML file:
PdfReader pdfReader = new PdfReader(inputPdf);
using (MemoryStream ms = new MemoryStream())
{
using (PdfStamper stamper = new PdfStamper(pdfReader, ms, '\0', true))
{
XfaForm xfaForm = new XfaForm(pdfReader);
XmlDocument doc = new XmlDocument();
doc.Load(inputXml);
xfaForm.DomDocument = doc;
xfaForm.Changed = true;
XfaForm.SetXfa(xfaForm, stamper.Reader, stamper.Writer);
}
var bytes = ms.ToArray();
System.IO.File.WriteAllBytes(outputPdf, bytes);
}
inputPdf is the path to the original empty PDF file.
inputXml is the path to the XML file extracted from the filled out PDF file. This is the entire XML file, and not just the datasets section.
What's interesting is that if I create the PdfStamper object like this instead:
new PdfStamper(pdfReader, ms);
then I see the data in the fields, but of course then I have the associated issues with not appending.
Any suggestions on what I might be doing wrong? I just can't seem to get any of the changes to the DomDocument to save.

Why does my custom XML not carry over to a new version of a DOCX file when Word saves it?

I'm adding in some custom XML to a docx for tracking it inside an application I'm writing.
I've manually done it via opening the Word Document via a ZIP library, and via the official Open XML SDK route. Both have the same outcome of my XML being inserted into customXml folder in the document. The document opens fine in Word for both of these methods, and the XML is present.
BUT when I then save the document as MyDoc2.docx for example all my XML disappears.
What am I doing wrong?
Microsoft links I've been following:
http://msdn.microsoft.com/en-us/library/bb608597.aspx
http://msdn.microsoft.com/en-us/library/bb608612.aspx
And the code I've taken from the Open XML SDK 2.0:
public static void AddNewPart(string document, string fileName)
{
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(document, true))
{
MainDocumentPart mainPart = wordDoc.MainDocumentPart;
CustomXmlPart myXmlPart = mainPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);
using (FileStream stream = new FileStream(fileName, FileMode.Open))
{
myXmlPart.FeedData(stream);
}
}
}
Thanks,
John
Ok,so I managed to find the following article Using Custom XML Part as DataStore on openxmldeveloper.org, and have stripped out the unnecessary code so that it inserts and retains custom XML:
static void Main(string[] args)
{
using (WordprocessingDocument doc = WordprocessingDocument.Open("Test.docx", true, new OpenSettings()))
{
int customXmlPartsCount = doc.MainDocumentPart.GetPartsCountOfType<CustomXmlPart>();
if (customXmlPartsCount == 0)
{
CustomXmlPart customXmlPersonDataSourcePart = doc.MainDocumentPart.AddNewPart<CustomXmlPart>("application/xml", null);
using (FileStream stream = new FileStream("Test.xml", FileMode.Open))
{
customXmlPersonDataSourcePart.FeedData(stream);
}
CustomXmlPropertiesPart customXmlPersonPropertiesDataSourcePart = customXmlPersonDataSourcePart
.AddNewPart<CustomXmlPropertiesPart>("Rd3c4172d526e4b2384ade4b889302c76");
Ds.DataStoreItem dataStoreItem1 = new Ds.DataStoreItem() { ItemId = "{88e81a45-98c0-4d79-952a-e8203ce59aac}" };
customXmlPersonPropertiesDataSourcePart.DataStoreItem = dataStoreItem1;
}
}
}
So all the examples from Microsoft work as long as you don't modify the file. The problem appears to be because we don't setup the relationship with the Main Document.

Manipulating Word 2007 Document XML in C#

I am trying to manipulate the XML of a Word 2007 document in C#. I have managed to find and manipulate the node that I want but now I can't seem to figure out how to save it back. Here is what I am trying:
// Open the document from memoryStream
Package pkgFile = Package.Open(memoryStream, FileMode.Open, FileAccess.ReadWrite);
PackageRelationshipCollection pkgrcOfficeDocument = pkgFile.GetRelationshipsByType(strRelRoot);
foreach (PackageRelationship pkgr in pkgrcOfficeDocument)
{
if (pkgr.SourceUri.OriginalString == "/")
{
Uri uriData = new Uri("/word/document.xml", UriKind.Relative);
PackagePart pkgprtData = pkgFile.GetPart(uriData);
XmlDocument doc = new XmlDocument();
doc.Load(pkgprtData.GetStream());
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("w", nsUri);
XmlNodeList nodes = doc.SelectNodes("//w:body/w:p/w:r/w:t", nsManager);
foreach (XmlNode node in nodes)
{
if (node.InnerText == "{{TextToChange}}")
{
node.InnerText = "success";
}
}
if (pkgFile.PartExists(uriData))
{
// Delete template "/customXML/item1.xml" part
pkgFile.DeletePart(uriData);
}
PackagePart newPkgprtData = pkgFile.CreatePart(uriData, "application/xml");
StreamWriter partWrtr = new StreamWriter(newPkgprtData.GetStream(FileMode.Create, FileAccess.Write));
doc.Save(partWrtr);
partWrtr.Close();
}
}
pkgFile.Close();
I get the error 'Memory stream is not expandable'. Any ideas?
I would recommend that you use Open XML SDK instead of hacking the format by yourself.
Using OpenXML SDK 2.0, I do this:
public void SearchAndReplace(Dictionary<string, string> tokens)
{
using (WordprocessingDocument doc = WordprocessingDocument.Open(_filename, true))
ProcessDocument(doc, tokens);
}
private string GetPartAsString(OpenXmlPart part)
{
string text = String.Empty;
using (StreamReader sr = new StreamReader(part.GetStream()))
{
text = sr.ReadToEnd();
}
return text;
}
private void SavePart(OpenXmlPart part, string text)
{
using (StreamWriter sw = new StreamWriter(part.GetStream(FileMode.Create)))
{
sw.Write(text);
}
}
private void ProcessDocument(WordprocessingDocument doc, Dictionary<string, string> tokenDict)
{
ProcessPart(doc.MainDocumentPart, tokenDict);
foreach (var part in doc.MainDocumentPart.HeaderParts)
{
ProcessPart(part, tokenDict);
}
foreach (var part in doc.MainDocumentPart.FooterParts)
{
ProcessPart(part, tokenDict);
}
}
private void ProcessPart(OpenXmlPart part, Dictionary<string, string> tokenDict)
{
string docText = GetPartAsString(part);
foreach (var keyval in tokenDict)
{
Regex expr = new Regex(_starttag + keyval.Key + _endtag);
docText = expr.Replace(docText, keyval.Value);
}
SavePart(part, docText);
}
From this you could write a GetPartAsXmlDocument, do what you want with it, and then stream it back with SavePart(part, xmlString).
Hope this helps!
You should use the OpenXML SDK to work on docx files and not write your own wrapper.
Getting Started with the Open XML SDK 2.0 for Microsoft Office
Introducing the Office (2007) Open XML File Formats
How to: Manipulate Office Open XML Formats Documents
Manipulate Docx with C# without Microsoft Word installed with OpenXML SDK
The problem appears to be doc.Save(partWrtr), which is built using newPkgprtData, which is built using pkgFile, which loads from a memory stream... Because you loaded from a memory stream it's trying to save the document back to that same memory stream. This leads to the error you are seeing.
Instead of saving it to the memory stream try saving it to a new file or to a new memory stream.
The short and simple answer to the issue with getting 'Memory stream is not expandable' is:
Do not open the document from memoryStream.
So in that respect the earlier answer is correct, simply open a file instead.
Opening from MemoryStream editing the document (in my experience) easy lead to 'Memory stream is not expandable'.
I suppose the message appears when one do edits that requires the memory stream to expand.
I have found that I can do some edits but not anything that add to the size.
So, f.ex deleting a custom xml part is ok but adding one and some data is not.
So if you actually need to open a memory stream you must figure out how to open an expandable MemoryStream if you want to add to it.
I have a need for this and hope to find a solution.
Stein-Tore Erdal
PS: just noticed the answer from "Jan 26 '11 at 15:18".
Don't think that is the answer in all situations.
I get the error when trying this:
var ms = new MemoryStream(bytes);
using (WordprocessingDocument wd = WordprocessingDocument.Open(ms, true))
{
...
using (MemoryStream msData = new MemoryStream())
{
xdoc.Save(msData);
msData.Position = 0;
ourCxp.FeedData(msData); // Memory stream is not expandable.

Categories