What is the best way to remove all merge fields from a word 2010 document using openxml sdk 2.0, and replace them with a simple text? I have some difficulties to remove them cleanly. Have tried to remove all Run objects that includes a FieldCode with a "MERGEFIELD" defined, and appended a new Run with my text. But I am missing something crucial since the field seems to stay defined for this element.
Ok, I solved this somehow. I pick out every paragraph that contains a FieldCode that contains the text "MERGEFIELD" and run through every Run, pushing all other types onto a List. When I discover a FieldChar End type, I roll back and replace the four Runs making up the merge field with a single Run containing a Text node. The only problem I have left now is nested fields. E.g. Merge field inside an if test.
Related
I want to be able to place 2 paragraphs after text in a MSWord document I am working on. It is all written in a custom compiler setup as an addin in word. Unfortunately I am a C# beginner and I cannot make heads or tails of it anymore.
Will Microsoft.Office.Interop.Word.Paragraph.Next(ref object count); be able to add even 1 paragraph if added in some way after text? E.g.
Somehow declaring Paragraph2 as 2 x Microsoft.Office.Interop.Word.Paragraph.Next(ref object count); and would be used as follows
string text = "Hello, World!";
BC.SetThisField(text + Paragraph2);
Resulting in (Pilcrows to represent what it would look like with paragraph characters showing):
Hello, World!¶
¶
The link(below) to Microsoft's documentation on this has simply led me down a road of researching ref, object and their definition of count which is apparently 1. I have been unsuccessful in using even the default of what is supposed to be.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.interop.word.paragraph.next?view=word-pia#Microsoft_Office_Interop_Word_Paragraph_Next_System_Object__
I have thought, according to similar questions asked on stackoverflow that I should use Paragraphs.Add(Object) Method instead, although I would still be stuck either way.
If I understand the question correctly, you want to insert text, followed by two paragraph marks? The simplest C# for that would be:
string text = "Hello, World!\n\n";
BC.SetThisField(text);
\n or \r generates a paragraph mark (ANSI 13) in Word.
Another possibility is to concatenate char(13).
There's also InsertParagraphAfter, but that requires an additional
step and a target Range object.
The Paragraphs.Add() will insert a single paragraph, but in order
to insert more than one it would have to be run in a loop and would
also require a Range object as the target.
Microsoft.Office.Interop.Word.Paragraph.Next(ref object count); will not insert paragraphs and will return an error if the code tries to perform an action with a paragraph that does not exist. (If code tried to assign text to the paragraph, for example.)
I have an issue I've stuck with for over a year now. I made a Forms application in VB.net which allows the user to type in some information and select items which represent docx-files with tables with special formatting, pictures and other formatting quirks in them.
At the end the software creates a Word document via Office.Interop, using the information the user provided in text fields in the Forms and the items they selected (e.g. it creates a table in Word, listing the user's selections with some extra info) and then appends the content from multiple docx-files depending on the user's selection to the document created via Interop.
The problem is: To achieve this I had to use a pretty dirty method:
I open the respective docx-files, select all content (Range.Wholestory()) and copy it (Range.Copy()). Then I insert this content from the clipboard into my newly created document with the following option:
Selection.PasteAndFormat (wdFormatOriginalFormatting)
This produces a satisfactory result but it feels super dirty since it uses the user's clipboard (which I save at the beginning of the runtime and restore at the end).
I originally tried to use the Selection.InsertFile-Method and tried this again today but it completely screws the formatting.
When the content of the docx is inserted this way it neither has the formatting of the original docx nor the one of the file I created with the program. E.g. the SpaceBefore and SpaceAfter values are wrong, even if I explicitly define them in my created file. Changing the formatting afterwards is no option since the source files contain a lot of special formatting and can change all the time.
Another factor which makes it hard: I cannot save the file before it is presented to the user, using temp folder is not an option in the environment this application is deployed into, so basically everything happens in RAM.
Summary:
Basically what I want is to create the same outcome as with my "Copy and Paste" method utilizing the OriginalFormatting WITHOUT using the clipboard. The problem is, the InsertFile-Method doesn't provide an option for the formatting.
Any idea or help would be greatly appreciated.
Edit:
The FormattedText option as suggested by Rich Michaels produces the same result as the InsertFile-Method. Here is the relevant part of what I did (word is the Microsoft.Office.Interop.Word.Application):
#Opening the source file
Dim doctemp As Microsoft.Office.Interop.Word.Document
doctemp = word.Documents.Open(doctempfilepath)
#Selecting whole document; this is what I did for the "Copy/Paste"-Method, too
doctemp.Range.WholeStory()
Dim insert_range As wordoptions.Range
doc_destination.Activate()
#Jumping to the end and selecting the range
word.Selection.EndKey(Unit:=Microsoft.Office.Interop.Word.WdUnits.wdStory)
insert_range = word.Selection.Range
#Inserting the text
insert_range.FormattedText = doctemp.Range.FormattedText
doctemp.Close(False)
This is the problem:
Use the Range.FormattedText property. It doesn't touch the clipboard and it maintains the source formatting. The process is ...
Set the range in the Source document you want "copied" and set the insertion point in the Destination document and then,
DestinationRange.FormattedText = SourceRange.FormattedText
I have an ASP WebForms app where I use a word template that contains merge fields, to replace them with data extracted from the database. The app works great, the word document is exported, but when trying to print the document, one of the merge fields, which exists in the header, loses it's value and restores to the initial merge field name. Is this something that has to be fixed from the application's code or is this a word settings issue.
Any help is greatly appreciated.
Thank you!
I have managed to solve this problem using OpenXML Productivity Tool. It turns out that you can't add a merge field in the header, so what I did was to put it inside of a textbox. I forgot to mention that part in the initial description. Thus, the text element was buried deep in open xml. When I managed to find it and log what was inside, I found out that I inserted the MERGEFIELD <> MERGEFORMAT. Every time I tried to insert the value that I wanted in this <>, it got reset when I hit print preview. So what I did, based on a suggestion from someone who had a similar problem, was to delete this textbox and create a clean one where I only entered "Test". It needs to have a string inside so that open xml created the element Text (instrTxt).
In C# I did this:
foreach (var hPart in firstDoc.MainDocumentPart.HeaderParts)
{
foreach (var txt in hPart.Header.Descendants<Text>())
{
if(txt.Text == "Test")
{
txt.Text = "My custom text";
}
}
}
So for each header part (because I can't tell for sure in which one it is..it could even be in multiple ones), get all descendants of type text.
I got a few more than I wanted. I also got a few that contained the page number (since I have it in the header as well). So I added an if to check if it's the text element that I wanted. Once I found it, I added the text I wanted.
So long story short, instead of using a merge field in the header, I just used a text. Perhaps it's not the most efficient way of doing this. Maybe the question still remains, (if I could have inserted a merge field in the header and actually made it work without having word reset the value upon print preview? idk), but this worked for me.
I'm working on an application which has to create word documents with the use of Office Open XML SDK 2.5. The idea that I'm having now is that I will start from a template with an empty body (so I have all the namespaces etc. defined already), and add Paragraphsto it. If I need images I will add the ImageParts and try to give the ImagePart the Id present in the predefined paragraphpart which will contain the image. I will store the paragraphs as xml in a database, fetch the ones I need, fill in/modify some values if needed and insert them into my word document. But this is the tricky part, how can I easily insert them in a way so I don't have to query on their content to later on find one of the paragraphs? In other words, I need Id's. I have some options in mind:
For each possible paragraph I have, manually create a SdtBlock. This SdtBlock will have an Id which matches the Id of each paragraph in the database. This seems like a lot of manual work though, and I'd rather be able to create future word documents easier...
I chose this approach but I insert Building Blocks which can be stored in templates with a specific tagname.
Create the paragraphs, copy the xml from the developer tool, and manually add a ParagraphId. This seems even more of a nightmare though, because for every future new paragraphs I will have to create new Id's etc. Also it would be impossible to insert tables as there is no way (afaik) to give those an Id.
Work with bookmarks to know where to insert the data. I don't really like this either as bookmarks are visible for everyone. I know I can replace them, but then I don't have any way to identify individual paragraphs later on.
**** my database and just add everything in the template :D Remove the paragraphs I don't need by deleting the bookmarks with their content. This idea seems the worst of all though as I don't want to depend on having a templatefile with all possible content per word-file I need to generate.
Anyone with experience in OpenXml who knows which approach would be the best? Maybe there is another approach which is better and I have completely overlooked? The ideal solution would be that I can add Ids in Office Word but that's a no-go as I haven't found anything to do that yet.
Thanks in advance!
Content Controls (std) were designed for this, although I'm not sure the designers ever contemplated "targeting" each and every paragraph in the document...
Back in the 2003/2007 days it was possible to add custom XML mark-up to a Word document, which would have been exactly what you're looking for. But Microsoft lost a patent court case around 2009 and had to pull the functionality. So content controls are really your only good choice.
Your approach could, possibly, be combined with the BuildingBlocks concept. BuildingBlocks are Word content stored in a Word template file as valid Word Open XML. They can be assigned to "galleries" and categorized. There is a Content Control of type BuildingBlock that can be associated with a specific Gallery and Category which might help you to a certain extent and would be an alternative to storing content in a database.
Ok, I did a small research, you can do it in strict OpenXML, but only before you open your file in Word. Word will remove everything it cannot read.
using (WordprocessingDocument document = WordprocessingDocument.Open(path, true)) {
document.MainDocumentPart.Document.Body.Ancestors().First()
.SetAttribute(new OpenXmlAttribute() {
LocalName = "someIdName",
Value = "111" });
}
Here, for example, I set attribute "someIdName", which doesn't exits in OpenXML, to some random element. You can set it anywhere and use it as id
I'm trying to use the OpenXML SDK and the samples on Microsoft's pages to replace placeholders with real content in Word documents.
It used to work as described here, but after editing the template file in Word adding headers and footers it stopped working. I wondered why and some debugging showed me this:
Which is the content of texts in this piece of code:
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(DocumentFile, true))
{
var texts = wordDoc.MainDocumentPart.Document.Body.Descendants<Text>().ToList();
}
So what I see here is that the body of the document is "fragmented", even though in Word the content looks like this:
Can somebody tell me how I can get around this?
I have been asked what I'm trying to achieve. Basically I want to replace user defined "placeholders" with real content. I want to treat the Word document like a template. The placeholders can be anything. In my above example they look like {var:Template1}, but that's just something I'm playing with. It could basically be any word.
So for example if the document contains the following paragraph:
Do not use the name USER_NAME
The user should be able to replace the USER_NAME placeholder with the word admin for example, keeping the formatting intact. The result should be
Do not use the name admin
The problem I see with working on paragraph level, concatenating the content and then replacing the content of the paragraph, I fear I'm losing the formatting that should be kept as in
Do not use the name admin
Various things can fragment text runs. Most frequently proofing markup (as apparently is the case here, where there are "squigglies") or rsid (used to compare documents and track who edited what, when), as well as the "Go back" bookmark Word sets in the background. These become readily apparent if you view the underlying WordOpenXML (using the Open XML SDK Productivity Tool, for example) in the document.xml "part".
It usually helps to go an element level "higher". In this case, get the list of Paragraph descendants and from there get all the Text descendants and concatenate their InnerText.
OpenXML is indeed fragmenting your text:
I created a library that does exactly this : render a word template with the values from a JSON.
From the documenation of docxtemplater :
Why you should use a library for this
Docx is a zipped format that contains some xml. If you want to build a simple replace {tag} by value system, it can already become complicated, because the {tag} is internally separated into <w:t>{</w:t><w:t>tag</w:t><w:t>}</w:t>. If you want to embed loops to iterate over an array, it becomes a real hassle.
The library basically will do the following to keep formatting :
If the text is :
<w:t>Hello</w:t>
<w:t>{name</w:t>
<w:t>} !</w:t>
<w:t>How are you ?</w:t>
The result would be :
<w:t>Hello</w:t>
<w:t>John !</w:t>
<w:t>How are you ?</w:t>
You also have to replace the tag by <w:t xml:space=\"preserve\"> to ensure that the space is not stripped out if they is any in your variables.