Underline form field value using itextsharp - c#

I have an application that uses itextsharp to fill PDF form fields.
I got new requirement from the customer to allow underlining the fields values.
I have read many posts including answers to questions in this site but I could't figure out a way to do it.
Current my code does the following:
Creates a PDFStamper instance
Get the form fields using stamper.AcroFields property
Set the field value using the AcroFields.SetFieldRichValue() method.
But when I am opening the PDF the field is empty.
I verified that the field is set as rich text in the PDF itself.
Any idea what I am doing wrong ?
Here is a snnipest of my code:
FileStream stream = File.Open(targetFile, FileMode.Create);
var pdfStamper = new PdfStamper(new PdfReader(sourceFile), stream);
// Iterate the fields in the PDF
foreach (var fieldName in pdfStamper.AcroFields.Fields.Keys)
{
// Get the field value of the current field
var fieldValue = "<?xml version=\"1.0\"?><body xmlns=\"http://www.w3.org/1999/xtml\" xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\" xfa:contentType=\"text/html\" xfa:APIVersion=\"Acrobat:8.0.0\" xfa:spec=\"2.4\"><p style=\"text-align:left\"><b><i>Here is some bold italic text</i></b></p><p style= \"font-size:16pt\">This text uses default text state parameters but changes the font size to 16.</p></body>"
// Set the field value
if (String.IsNullOrEmpty(fieldValue) == false)
{
pdfStamper.AcroFields.SetFieldRichValue(key, fieldValue);
}
}
Edit:
I have revised my code based on Mark Storer's post to a question (http://stackoverflow.com/questions/1454701/adding-rich-text-to-an-acrofield-in-itextsharp). The new code is:
// Create reader to read the source file
var reader = new PdfReader(sourceFile);
// Create a stream for the generated file
var stream = File.Open(targetFile, FileMode.Create);
// Create stamper to generate the new file
var pdfStamper = new PdfStamper(reader, stream);
// Field name and value
var fieldName = "myfield";
var fieldValue = "<?xml version=\"1.0\"?><body xfa:APIVersion=\"Acroform:2.7.0.0\" xfa:spec=\"2.1\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\"><p dir=\"ltr\" style=\"margin-top:0pt;margin-bottom:0pt;font-family:Helvetica;font-size:12pt\"><b>write line1 bold</b></p</body>";
// Output stream for the temporary file that should contain the apearance of the field
var msOutput = new FileStream(#"d:\temp.pdf", FileMode.Create);
// string reader to read the field value
var textReader = new StringReader(fieldValue);
// Create new document
var document = new Document(pdfStamper.AcroFields.GetFieldPositions(fieldName)[0].position);
// writer for the new doucment
var writer = PdfWriter.GetInstance(document, msOutput);
// Open the document
document.Open();
// Get elements to append to the doucment
var list = HTMLWorker.ParseToList(textReader, null);
// Append elements to the doucment
foreach (var element in list)
{
document.Add(element);
}
// close the documnet
document.Close();
// Append push button that contains the generated content as its apearance
// this approach is based on the suggestion from Mark storer that can be found in:
// http://stackoverflow.com/questions/1454701/adding-rich-text-to-an-acrofield-in-itextsharp
var button = new PushbuttonField(pdfStamper.Writer, pdfStamper.AcroFields.GetFieldPositions(fieldName)[0].position, fieldName + "_")
{
Layout = PushbuttonField.LAYOUT_ICON_ONLY,
BackgroundColor = null,
Template = pdfStamper.Writer.GetImportedPage(new PdfReader(targetFile, 1)
};
pdfStamper.AddAnnotation(button.Field, 1);
pdfStamper.FormFlattening = true;
pdfStamper.Close();
pdfStamper.Dispose();
But the problem now is that the temporary document contains no content....
Any ideas ?

There's a lot of code above, almost 400 lines. In the future, try to distill everything down to a very simple and reproducible test case.
When using SetFieldRichValue you also need to set the PdfStamper's AcroFields.GenerateAppearances property to false:
stamper.AcroFields.GenerateAppearances = false;
You can read more about this here along with some caveats and other workarounds.

I managed to resolve it with little trick - I used Anchor (hyperling) element inside ColumnText element and position it above the form field. The anchor is displayed with underline by default. In order to avoid the hand marker when the user hover the mouse on the anchor, I set the "Reference" property of the anchor to null.
Here is the code I used:
// Create reader
var reader = new PdfReader(sourceFilePathAndName);
// Create stream for the target file
var stream = File.Open(targetFilePathAndName, FileMode.Create);
// Create the stamper
var pdfStamper = new PdfStamper(reader, stream);
const string fieldName = "MyField";
// Get the position of the field
var targetPosition = pdfStamper.AcroFields.GetFieldPositions(fieldName)[0].position;
// Set the font
var fontNormal = FontFactory.GetFont("Arial", 16, Font.UNDERLINE, BaseColor.BLACK);
// Create the anchor
var url = new Anchor("some default text", fontNormal) { Reference = null };
// Create the element that will contain the anchor and allow to position it anywhere on the document
var data = new ColumnText(pdfStamper.GetOverContent(1));
// Add the anchor to its container
data.SetSimpleColumn(url, targetPosition.Left, targetPosition.Bottom, targetPosition.Right, targetPosition.Top, 0, 0);
// Write the content to the document
data.Go();
pdfStamper.FormFlattening = true;
pdfStamper.Close();
pdfStamper.Dispose();

Related

Modifying Hyperlink with openxml

I'm trying to modify a hyperlink inside a word document. The hyperlink is originally pointing to a bookmark in a external document. What I want to do is change it to point to an internal bookmark that as the same Anchor.
Here's the code I use... It seem to work when I what the variables but when I look at the saved document it is exactly like the original.
What is the reason my chances don't persist?
// read file specified in stream
MemoryStream stream = new MemoryStream(File.ReadAllBytes("C:\\TEMPO\\smartbook\\text1.docx"));
WordprocessingDocument doc = WordprocessingDocument.Open(stream, true);
MainDocumentPart mainPart = doc.MainDocumentPart;
// The first hyperlink -- it happens to be the one I want to modify
Hyperlink hLink = mainPart.Document.Body.Descendants<Hyperlink>().FirstOrDefault();
if (hLink != null)
{
// get hyperlink's relation Id (where path stores)
string relationId = hLink.Id;
if (relationId != string.Empty)
{
// get current relation
HyperlinkRelationship hr = mainPart.HyperlinkRelationships.Where(a => a.Id == relationId).FirstOrDefault();
if (hr != null)
{
// remove current relation
mainPart.DeleteReferenceRelationship(hr);
// add new relation with relation
// mainPart.AddHyperlinkRelationship(new Uri("C:\\TEMPO\\smartbook\\test.docx"), false, relationId);
}
}
// change hyperlink attributes
hLink.DocLocation = "#";
hLink.Id = "";
hLink.Anchor = "TEST";
}
// save stream to a new file
File.WriteAllBytes("C:\\TEMPO\\smartbook\\test.docx", stream.ToArray());
doc.Close();
You have not yet saved your OpenXmlPackage when you write the stream ...
// types that implement IDisposable are better wrapped in a using statement
using(var stream = new MemoryStream(File.ReadAllBytes(#"C:\TEMPO\smartbook\text1.docx")))
{
using(var doc = WordprocessingDocument.Open(stream, true))
{
// do all your changes
// call doc.Close because that SAVES your changes to the stream
doc.Close();
}
// save stream to a new file
File.WriteAllBytes(#"C:\TEMPO\smartbook\test.docx", stream.ToArray());
}
The Close method states explicitly:
Saves and closes the OpenXml package plus all underlying part streams.
You could also set the AutoSave property to true in which case the OpenXMLPackage will be saved when Dispose is called. The using statement I used above would guarantee that that will happen.

How to add in reply to annotation using iTextSharp

I am trying to add a sticky note reply to in pdf using iTextSharp. I am able to create a new annotation in the pdf. But i cannot link it as child of an already existing annotation. I copied most of the properties in parent to its child. I copied it by analyzing the properties of a reply, by manually adding a reply from Adobe Reader. What I am missing is the property /IRT. It needs a reference to the parent popup. Like /IRT 16 0 R.
Below is the code i am trying.
private void annotateReplyPdf()
{
string outputFile = #"D:\temp\temp.pdf";
// Creating iTextSharp.text.pdf.PdfReader object to read the Existing PDF Document
using (PdfReader reader = new PdfReader(FILE_NAME))
{
using (FileStream fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
// Creating iTextSharp.text.pdf.PdfStamper object to write Data from iTextSharp.text.pdf.PdfReader object to FileStream object
using (PdfStamper stamper = new PdfStamper(reader, fs))
{
//get page 1
PdfDictionary pageDic = reader.GetPageN(1);
//get annotations in page 1
PdfArray pageAnnotsArray = pageDic.GetAsArray(PdfName.ANNOTS);
if (pageAnnotsArray != null)
{
PdfDictionary curAnnotDic = pageAnnotsArray.GetAsDict(0);
PdfArray rect = curAnnotDic.GetAsArray(PdfName.RECT);
Rectangle rectangle = new Rectangle(float.Parse(rect[0].ToString()), float.Parse(rect[1].ToString()), float.Parse(rect[2].ToString()), float.Parse(rect[3].ToString()));
PdfAnnotation newAnnot = new PdfAnnotation(stamper.Writer, rectangle);
newAnnot.Title = "john.conor";
var dtNow = DateTime.Now;
newAnnot.Put(PdfName.C, curAnnotDic.Get(PdfName.C));
newAnnot.Put(PdfName.CONTENTS, new PdfString("Reply using prog"));
newAnnot.Put(PdfName.CREATIONDATE, new PdfDate(dtNow));
// newAnnot.Put(PdfName.IRT, curAnnotDic.); stuck here
newAnnot.Put(PdfName.M, new PdfDate(dtNow));
newAnnot.Put(PdfName.NAME, curAnnotDic.Get(PdfName.NAME));
newAnnot.Put(PdfName.RC, curAnnotDic.Get(PdfName.RC));
newAnnot.Put(PdfName.SUBTYPE, PdfName.TEXT);
newAnnot.Put(PdfName.SUBJECT, curAnnotDic.Get(PdfName.SUBJECT));
stamper.AddAnnotation(newAnnot, 1);
}
}
}
}
}
The methods I have used might not be accurate or efficient, as most of the code were found by trial and error and checking other similar examples(also checking the pdf specification).
Can somebody please fill that code, which does the magic.
note: SO question doesn't provide a code for the answer.
Please take a look at the AddInReplyTo example.
We have a file named hello_sticky_note.pdf that looks like this:
I am going to skip the method to detect the annotation of the sticky note (in your question, you already have this code). In my example, I know that this annotation is the first entry in the /Annots array (the annotation with index 0).
This is how I'm going to add an "in reply to" annotation:
public void manipulatePdf(String src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfDictionary page = reader.getPageN(1);
PdfArray annots = page.getAsArray(PdfName.ANNOTS);
PdfDictionary sticky = annots.getAsDict(0);
PdfArray stickyRect = sticky.getAsArray(PdfName.RECT);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
PdfWriter writer = stamper.getWriter();
Rectangle stickyRectangle = new Rectangle(
stickyRect.getAsNumber(0).floatValue(), stickyRect.getAsNumber(1).floatValue(),
stickyRect.getAsNumber(2).floatValue(), stickyRect.getAsNumber(3).floatValue()
);
PdfAnnotation replySticky = PdfAnnotation.createText(
writer, stickyRectangle, "Reply", "Hello PDF", true, "Comment");
replySticky.put(PdfName.IRT, annots.getAsIndirectObject(0));
stamper.addAnnotation(replySticky, 1);
stamper.close();
}
Just like you, I get the original annotation (in my code, it's named sticky) and I get the position of that annotation (stickyRect). I create a stickyRectangle object in a slightly different way than you do (my way is better, but that doesn't matter too much) and I use that stickyRectangle to create a new PdfAnnotation named replySticky.
That's what you already have. Now I add the missing part:
replySticky.Put(PdfName.IRT, annots.GetAsIndirectObject(0));
In your code, you add the annotation dictionary, but what you actually need is the reference to that dictionary.
The resulting PDF looks like hello_in_reply_to.pdf:

XpsDocument ignores my height settings

I am facing a problem when I try to declare a PageHeight on an XPS Document.
My code so far looks like this:
private FixedDocumentSequence GetDocument(DocumentPaginator paginator)
{
FixedDocumentSequence document = null;
string tempFileName = System.IO.Path.GetTempFileName();
File.Delete(tempFileName);
using (var xpsDocument = new XpsDocument(tempFileName, FileAccess.ReadWrite, CompressionOption.NotCompressed))
{
var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
var printTicket = new PrintTicket
{
PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA4, paginator.PageSize.Width, paginator.PageSize.Height),
PageBorderless = PageBorderless.Borderless,
PageMediaType = PageMediaType.None,
};
writer.Write(paginator, printTicket);
//paginator.PageSize.Height = 1122
var d = xpsDocument.GetFixedDocumentSequence();
//d.DocumentPaginator.PageSize.Height = 1056
document = d;
}
return document;
}
The Problem is that I am declaring a PageSize Height of 1122 which I am getting from this code:
var pd = new PrintDialog();
var sz = new Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);
but when I look into the property
d.DocumentPaginator.PageSize.Height
I can see that the Height is 1056, this height happens to be the Height of the NorthAmericanLetter Page format height, I am already specifying a specific printTicket with an explicit PageMediaSize what else could be wrong?
Edit:
Here is my edited code, but this give the same result:
private FixedDocumentSequence GetDocument(DocumentPaginator paginator)
{
FixedDocumentSequence document = null;
string oldTempFileName = System.IO.Path.GetTempFileName();
File.Delete(oldTempFileName);
XpsDocument oldXpsDocument;
using (oldXpsDocument = new XpsDocument(oldTempFileName, FileAccess.ReadWrite, CompressionOption.NotCompressed))
{
var writer = XpsDocument.CreateXpsDocumentWriter(oldXpsDocument);
var printTicket = new PrintTicket
{
PageMediaSize = new PageMediaSize(PageMediaSizeName.ISOA4, paginator.PageSize.Width, paginator.PageSize.Height),
PageBorderless = PageBorderless.Borderless,
PageMediaType = PageMediaType.None,
};
writer.Write(paginator, printTicket);
}
//string newTempFileName = System.IO.Path.GetTempFileName();
//File.Delete(newTempFileName);
using (var newXpsDocument = new XpsDocument(oldTempFileName, FileAccess.Read, CompressionOption.NotCompressed))
{
var d = newXpsDocument.GetFixedDocumentSequence();
document = d;
}
return document;
}
I looked into the file that got created with the first using block and I still can see that the height of the pages is 1056, output of the 1.fpage file:
<FixedPage
xmlns="http://schemas.microsoft.com/xps/2005/06" xmlns:x="http://schemas.microsoft.com/xps/2005/06/resourcedictionary-key"
xml:lang="und"
Width="816" Height="1056">
<FixedPage.Resources>
<ResourceDictionary>
<LinearGradientBrush x:Key="b0" StartPoint="33,0" EndPoint="33,23" ColorInterpolationMode="SRgbLinearInterpolation" MappingMode="Absolute" SpreadMethod="Pad">
Edit2:
Found the a solution for my problem.
In my DocumentPaginator I had to override the GetPage Method and there I return a new DocumentPage(visual), this constructor has an overload that lets me set the PageSize only if I set it there its setting the PageSizes correctly.
http://msdn.microsoft.com/en-us/library/system.windows.documents.documentpage(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/ms597306(v=vs.110).aspx
My previous code:
public override DocumentPage GetPage(int pageNumber)
{
// create page element (PageTemplate is a custom usercontrol that can hold content)
var page = new PageTemplate(this, pageNumber + 1);
// arrange the elements on the page
page.Arrange(new Rect(0, 0, PageSize.Width, PageSize.Height));
// return new document page
return new DocumentPage(page);
}
Now with the second contructor:
public override DocumentPage GetPage(int pageNumber)
{
// create page element (PageTemplate is a custom usercontrol that can hold content)
var page = new PageTemplate(this, pageNumber + 1);
// arrange the elements on the page
page.Arrange(new Rect(0, 0, PageSize.Width, PageSize.Height));
// return new document page
return new DocumentPage(page, PageSize, new Rect(0, 0, 10, 10), new Rect());
}
The problem is that you're expecting the xpsDocument to have the same dimensions of the file that you created.
When you create an XpsDocument, its going to have a default FixedDocumentSequence. Since you're creating an XpsDocument, and not opening one, you're getting the default fixed document sequence, which is why you are seeing 1056 as the height--default that is the default.
However, if you look at the code example at the bottom of FixedDocumentSequence, you'll see LoadDocumentViewer, where it replaces an old XpsDocument with a new one.
When you call writer.Write(paginator, printTicket); it will write an XpsDocument to the file you specified using the PrintTicket you provided. Now you need to create an XpsDocument using that file path, and when you open it, you can then get the FixedDocumentSequence of that document and the sizes with match the PrintTicket properties. So in pseudo-code, you should do this:
string fileName = ...
using Create XpsDocument at fileName
Create XpsDocumentWriter
Create and setup PrintTicket
Write to the file using your DocumentPaginator and PrintTicket
using Create XpsDocument reading from fileName
document = opened XpsDocument GetFixedDocumentSequence()
Edit:
Good find in your last edit. I printed an XPS document using MS Word with the A4 sized setting and all the pages come up as 1056 x 816 also. I then created an XPS document using the A3 size and that comes up as 1587.36 x 1122.56. There is clearly something going on internally that we cannot see; my guess is that since the page sizes are similar in dimension, it just uses the 1056 x 816 dimensions? Who really knows... I would suggest filing a bug report to Microsoft.
In the meantime, maybe try to change d.DocumentPaginator.PageSize or even d.PrintTicket objects and try to get the page size to change that way. If there is a way to enumerate the FixedPage's you could change their settings manually too.

Change object's color inside existiong PDF with iTextSharp

Major part of my job is automation of engineering process, so I have to create simple program, that compares 2 different version of 1 drawn element, by overlapping drawings, in order to review differences. Drawings represent single sheet PDF files.
I'm using .Net Framework and C# 4.5;
iTextSharp library for editing PDF files;
Initially, I'm getting 2 files, read them and create the third one, that contains the result;
var file1 = "file1.pdf";
var file2 = "file2.pdf";
var result = "result.pdf";
using (Stream f1Stream = new FileStream(file1, FileMode.Open))
using (Stream f2Stream = new FileStream(file2, FileMode.Open))
using (Stream resultStream = new FileStream(result, FileMode.Create, FileAccess.ReadWrite))
using (PdfReader f2Reader = new PdfReader(f2Stream))
using (PdfReader f1Reader = new PdfReader(f1Stream))
{
PdfStamper pdfStamper = new PdfStamper(f1Reader, resultStream);
PdfContentByte pdfContentByte = pdfStamper.GetOverContent(1);
var page = pdfStamper.GetImportedPage(f2Reader, 1);
pdfContentByte.AddTemplate(page,2,2);
pdfStamper.Close();
}
The code above makes just that, but a few sequential questions are arising
I want to change the color of elements in the result file i.e. elements that come from the 1st drawing in green and the others from 2nd one - in red color. Maybe I have to change the color of entities in initial 2 PDFs and then to merge;
Initial files have layers, and because they are two sequential revision of the same construction element and differences between them are very few, they have identical layers. And I want to have " layerFoo " and " layerFoo# " in the result PDF. Maybe I have to rename all the layers in one the the 2 initial PDFs and then to merge them.
Аll suggestions are welcomed including usage of another library :)
--> Edit1
Big thanks to Chris Haas! You are absolutely right for token type and string value! iTextRUPS is great helping tool for understanding the structure of PDF files.
Following code is taken from the post that you pointed me out.
The following statement:
stream.SetData(System.Text.Encoding.ASCII.GetBytes(String.Join("\n", newBuf.ToArray())));
updates the stream of the file and then with
using (var fs = new FileStream(file2, FileMode.Create, FileAccess.Write, FileShare.None))
{
var stamper = new PdfStamper(reader, fs);
reader.SetPageContent(1,reader.GetPageContent(1));
stamper.Close();
}
the new file is created with updated stream.
I made 1 simple test file with only 2 lines, change their color and save back to a new file.
No problem!
After that, I tried the same simple operation with real file, that represents real drawing of construction element, the result file was less than half of the original and was broken.
What comes to mind is the updated stream is saved to the new file but the other information inside other containers is not saved, it's just the stream.
Because I stuck with that, I continue to the next step of investigation -> layers
I wrote this code in order to get available layers in a PDF file. I will try to insert more records into layers dictionary to see what will happen.
var resourcesReference = page.Get(PdfName.RESOURCES) as PdfIndirectReference;
var resources = PdfReader.GetPdfObject(resourcesReference) as PdfDictionary;
var propertiesObjhectReferences = resources.Get(PdfName.PROPERTIES);
var properties = PdfReader.GetPdfObject(propertiesObjhectReferences) as PdfDictionary;
foreach (var property in properties.Keys)
{
var layerReference = properties.Get(property);
var layerObject = PdfReader.GetPdfObject(layerReference) as PdfDictionary;
foreach (var key in layerObject.Keys)
{
if (key.ToString()!=PdfName.TYPE.ToString())
{
var layerName = layerObject.GetAsString(key).ToUnicodeString();
}
}
}
If I come back to my main goal from the top of the post, I tends to insert the stream and layers from first file into second in order to obtain result file, that contains objects from the previous 2, painted in different colors + layers from both.
Feel free to suggest me another, more simpler and beautiful solution! I will be happy if you revise my code and correct it! Thank You very much!
EDIT 2
I will simplify the work because the lack of time, just change the color of entities inside one PDF and put it on the background on the other.
const string Pdf = "file1.pdf";
var reader = new PdfReader(Pdf);
var page = reader.GetPageN(1);
var objectReference = page.Get(PdfName.CONTENTS) as PdfIndirectReference;
var stream = (PRStream)PdfReader.GetPdfObject(objectReference);
var streamBytes = PdfReader.GetStreamBytes(stream);
var tokenizer = new PRTokeniser(new RandomAccessFileOrArray(streamBytes));
var newBuf = new List<string>();
while (tokenizer.NextToken())
{
var token = tokenizer.StringValue;
newBuf.Add(token);
if (tokenizer.TokenType == PRTokeniser.TokType.OTHER
&& newBuf[newBuf.Count - 1].Equals("S", StringComparison.CurrentCultureIgnoreCase))
{
newBuf.Insert(newBuf.Count - 1, "0");
newBuf.Insert(newBuf.Count - 1, "1");
newBuf.Insert(newBuf.Count - 1, "1");
newBuf.Insert(newBuf.Count - 1, "RG");
}
}
var resultStream = String.Join("\n", newBuf.ToArray());
stream.SetData(System.Text.Encoding.ASCII.GetBytes(resultStream));
var file2 = Pdf.Insert(Pdf.Length - 4, "Result");
using (var fs = new FileStream(file2, FileMode.Create, FileAccess.Write, FileShare.None))
{
var stamper = new PdfStamper(reader, fs);
reader.SetPageContent(1, reader.GetPageContent(1));
stamper.Close();
}
Result PDF is broken and iTextRUPS throws exception when try to get the stream data from the page.

how can I put a content in a mergefield in docx

I'm developing a web application with asp.net and I have a file called Template.docx that works like a template to generate other reports. Inside this Template.docx I have some MergeFields (Title, CustomerName, Content, Footer, etc) to replace for some dynamic content in C#.
I would like to know, how can I put a content in a mergefield in docx ?
I don't know if MergeFields is the right way to do this or if there is another way. If you can suggest me, I appreciate!
PS: I have openxml referenced in my web application.
Edits:
private MemoryStream LoadFileIntoStream(string fileName)
{
MemoryStream memoryStream = new MemoryStream();
using (FileStream fileStream = File.OpenRead(fileName))
{
memoryStream.SetLength(fileStream.Length);
fileStream.Read(memoryStream.GetBuffer(), 0, (int) fileStream.Length);
memoryStream.Flush();
fileStream.Close();
}
return memoryStream;
}
public MemoryStream GenerateWord()
{
string templateDoc = "C:\\temp\\template.docx";
string reportFileName = "C:\\temp\\result.docx";
var reportStream = LoadFileIntoStream(templateDoc);
// Copy a new file name from template file
//File.Copy(templateDoc, reportFileName, true);
// Open the new Package
Package pkg = Package.Open(reportStream, FileMode.Open, FileAccess.ReadWrite);
// Specify the URI of the part to be read
Uri uri = new Uri("/word/document.xml", UriKind.Relative);
PackagePart part = pkg.GetPart(uri);
XmlDocument xmlMainXMLDoc = new XmlDocument();
xmlMainXMLDoc.Load(part.GetStream(FileMode.Open, FileAccess.Read));
// replace some keys inside xml (it will come from database, it's just a test)
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_customer", "My Customer Name");
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_title", "Report of Documents");
xmlMainXMLDoc.InnerXml = xmlMainXMLDoc.InnerXml.Replace("field_content", "Content of Document");
// Open the stream to write document
StreamWriter partWrt = new StreamWriter(part.GetStream(FileMode.Open, FileAccess.Write));
//doc.Save(partWrt);
xmlMainXMLDoc.Save(partWrt);
partWrt.Flush();
partWrt.Close();
reportStream.Flush();
pkg.Close();
return reportStream;
}
PS: When I convert MemoryStream to a file, I got a corrupted file. Thanks!
I know this is an old post, but I could not get the accepted answer to work for me. The project linked would not even compile (which someone has already commented in that link). Also, it seems to use other Nuget packages like WPFToolkit.
So I'm adding my answer here in case someone finds it useful. This only uses the OpenXML SDK 2.5 and also the WindowsBase v4. This works on MS Word 2010 and later.
string sourceFile = #"C:\Template.docx";
string targetFile = #"C:\Result.docx";
File.Copy(sourceFile, targetFile, true);
using (WordprocessingDocument document = WordprocessingDocument.Open(targetFile, true))
{
// If your sourceFile is a different type (e.g., .DOTX), you will need to change the target type like so:
document.ChangeDocumentType(WordprocessingDocumentType.Document);
// Get the MainPart of the document
MainDocumentPart mainPart = document.MainDocumentPart;
var mergeFields = mainPart.RootElement.Descendants<FieldCode>();
var mergeFieldName = "SenderFullName";
var replacementText = "John Smith";
ReplaceMergeFieldWithText(mergeFields, mergeFieldName, replacementText);
// Save the document
mainPart.Document.Save();
}
private void ReplaceMergeFieldWithText(IEnumerable<FieldCode> fields, string mergeFieldName, string replacementText)
{
var field = fields
.Where(f => f.InnerText.Contains(mergeFieldName))
.FirstOrDefault();
if (field != null)
{
// Get the Run that contains our FieldCode
// Then get the parent container of this Run
Run rFldCode = (Run)field.Parent;
// Get the three (3) other Runs that make up our merge field
Run rBegin = rFldCode.PreviousSibling<Run>();
Run rSep = rFldCode.NextSibling<Run>();
Run rText = rSep.NextSibling<Run>();
Run rEnd = rText.NextSibling<Run>();
// Get the Run that holds the Text element for our merge field
// Get the Text element and replace the text content
Text t = rText.GetFirstChild<Text>();
t.Text = replacementText;
// Remove all the four (4) Runs for our merge field
rFldCode.Remove();
rBegin.Remove();
rSep.Remove();
rEnd.Remove();
}
}
What the code above does is basically this:
Identify the 4 Runs that make up the merge field named "SenderFullName".
Identify the Run that contains the Text element for our merge field.
Remove the 4 Runs.
Update the text property of the Text element for our merge field.
UPDATE
For anyone interested, here is a simple static class I used to help me with replacing merge fields.
Frank Fajardo's answer was 99% of the way there for me, but it is important to note that MERGEFIELDS can be SimpleFields or FieldCodes.
In the case of SimpleFields, the text runs displayed to the user in the document are children of the SimpleField.
In the case of FieldCodes, the text runs shown to the user are between the runs containing FieldChars with the Separate and the End FieldCharValues. Occasionally, several text containing runs exist between the Separate and End Elements.
The code below deals with these problems. Further details of how to get all the MERGEFIELDS from the document, including the header and footer is available in a GitHub repository at https://github.com/mcshaz/SimPlanner/blob/master/SP.DTOs/Utilities/OpenXmlExtensions.cs
private static Run CreateSimpleTextRun(string text)
{
Run returnVar = new Run();
RunProperties runProp = new RunProperties();
runProp.Append(new NoProof());
returnVar.Append(runProp);
returnVar.Append(new Text() { Text = text });
return returnVar;
}
private static void InsertMergeFieldText(OpenXmlElement field, string replacementText)
{
var sf = field as SimpleField;
if (sf != null)
{
var textChildren = sf.Descendants<Text>();
textChildren.First().Text = replacementText;
foreach (var others in textChildren.Skip(1))
{
others.Remove();
}
}
else
{
var runs = GetAssociatedRuns((FieldCode)field);
var rEnd = runs[runs.Count - 1];
foreach (var r in runs
.SkipWhile(r => !r.ContainsCharType(FieldCharValues.Separate))
.Skip(1)
.TakeWhile(r=>r!= rEnd))
{
r.Remove();
}
rEnd.InsertBeforeSelf(CreateSimpleTextRun(replacementText));
}
}
private static IList<Run> GetAssociatedRuns(FieldCode fieldCode)
{
Run rFieldCode = (Run)fieldCode.Parent;
Run rBegin = rFieldCode.PreviousSibling<Run>();
Run rCurrent = rFieldCode.NextSibling<Run>();
var runs = new List<Run>(new[] { rBegin, rCurrent });
while (!rCurrent.ContainsCharType(FieldCharValues.End))
{
rCurrent = rCurrent.NextSibling<Run>();
runs.Add(rCurrent);
};
return runs;
}
private static bool ContainsCharType(this Run run, FieldCharValues fieldCharType)
{
var fc = run.GetFirstChild<FieldChar>();
return fc == null
? false
: fc.FieldCharType.Value == fieldCharType;
}
You could try http://www.codeproject.com/KB/office/Fill_Mergefields.aspx which uses the Open XML SDK to do this.

Categories

Resources