I have a template in Word that would be used to print out invoices.
Now, I would like to know how to create a Word Document programmatically and copy the template content into the new document so that I still have a clean template, then replace placeholders that I have typed by using Mail.Merge. I found similar Mail.Merge questions but most require Spire components and I am not interested since it needs to be paid for. I am only a student. Others though, actually doesn't help that much.
The problems I am facing now are as follows:
Create a Word document
Copy template content into new document
How to add placeholder names into MailMerge since I'm very confused about this.
Do MailMerge
Here is the current code that I have concocted, this is actually the first time I have used Interops
Document document = new Document();
Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
document = wordApp.Documents.Open(fileName);
string[] fieldNames = {"date_issued", "month_covered", "tuition", "lunchfee", "discount", "in_no", "student_no", "student_name", "amount_due", "amount_paid", "balance", "penalty", "status"};
string[] data = new string[25];
Range range;
document.MailMerge.Fields.Add(range, fieldNames[0]);
document.MailMerge.Execute();
I'm really confused on this part
document.MailMerge.Fields.Add(range, fieldNames[0]);
and I don't know what range is for
If your template is an actual template and not a plain Word Document (with the extension .dotx or .dot, and not .docx/.doc), you don't need to copy anything. Just create a new document based on the template:
var doc = wordApp.Documents.Add("put template path here");
This will have the contents of your template. If you have used the Word UI to setup a mailmerge in the template (including the location of the data for the merge), that will also be carried over into the new document.
Then you can execute the mailmerge from C#:
doc.MailMerge.Execute();
Related
I'm creating WordprocessingDocuments in C# with the Open XML SDK and then converting them to pdf. Initially, I was using Interop to save the document in PDF format, but now that is not an option. I found that LibreOffice can convert documents calling soffice.exe from cmd, and I had wonderful results with normal documents. Still, then, when I tested LibreOffice converter with my dynamic documents, the converter crashed.
I copied one of these documents and opened it with LibreOffice Writer, its structure was wrong, then I opened the same document with Microsoft Word and its structure was fine. Finally, I saved it with Microsoft Word and opened both documents as ZIP files as below:
This is the good one:
And this is the bad one:
I noticed that when I save the document in Microsoft Word, these Open XML parts (which I called "files" in an earlier version of this question) are appearing. When I open the document previously saved with Microsoft Word in LibreOffice, the document is fine again.
Thus, is there a way to generate these Open XML parts (inside the Word document) without opening Microsoft Word?
I use the following code (to check if it is creating all the files):
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();
// Add your docx content here
CreateParagraph(docBody);
CreateStyledParagraph(docBody);
CreateTable(docBody);
CreateList(docBody);
Paragraph pImg = new Paragraph();
ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Jpeg);
string imgPath = "https://cdn.pixabay.com/photo/2019/11/15/05/23/dog-4627679_960_720.png";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(imgPath);
req.UseDefaultCredentials = true;
req.PreAuthenticate = true;
req.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
imagePart.FeedData(resp.GetResponseStream());
// 1500000 and 1092000 are img width and height
Run rImg = new Run(DrawingManager(mainPart.GetIdOfPart(imagePart), "PictureName", 1500000, 1092000, string.Empty));
pImg.Append(rImg);
docBody.Append(pImg);
Paragraph pLink = new Paragraph();
// For the mainpart see above
pLink.Append(HyperLinkManager("http://YourLink", "My awesome link", mainPart));
docBody.Append(pLink);
mainPart.Document.Append(docBody);
mainPart.Document.Save();
wordDocument.Close();
}
result = Convert.ToBase64String(mem.ToArray());
}
The code above creates a Word document named Result.docx with the following structure:
But there aren't any other Open XML parts (like app.xml or styles.xml)
You need to make a difference between:
the Open XML standard and its minimum requirements on a WordprocessingDocument and
the "minimum" document created by Microsoft Word or other applications.
As per the standard, the minimum WordprocessingDocument only needs a main document part (MainDocumentPart, document.xml) with the following content:
<w:document xmlns:w="...">
<w:body>
<w:p />
</w:body>
</w:document>
Further parts such as the StyleDefinitionsPart (styles.xml) or the NumberingDefintionsPart (numbering.xml) are only required if you have styles or numbering, in which case you must explicitly create them in your code.
Next, looking at your sample code, it seems you are creating:
paragraphs that reference styles (see CreateStyledParagraph(docBody)), which would have to be defined in the StyleDefinitionsPart (styles.xml); and
numbered lists (e.g., CreateList(docBody)), which would have to be defined in the NumberingDefinitionsPart (numbering.xml).
However, your code neither creates a StyleDefinitionsPart nor a NumberingDefintionsPart, which means your document is likely not a valid Open XML document.
Now, Word is very forgiving and fixes various issues silently, ignoring parts of your Open XML markup (e.g., the styles you might have assigned to your paragraphs).
By contrast, depending on how fault-tolerant LibreOffice is, invalid Open XML markup might lead to a crash. For example, if LibreOffice simply assumes that a StyleDefinitionsPart exists when it finds an element like <w:pStyle w:val="MyStyleName" /> in your w:document and then does not check whether it gets a null reference when asking for the StyleDefinitionsPart, it could crash.
Finally, to add parts to your Word document, you would use the Open XML SDK as follows:
[Fact]
public void CanAddParts()
{
const string path = "Document.docx";
const WordprocessingDocumentType type = WordprocessingDocumentType.Document;
using WordprocessingDocument wordDocument = WordprocessingDocument.Create(path, type);
// Create minimum main document part.
MainDocumentPart mainDocumentPart = wordDocument.AddMainDocumentPart();
mainDocumentPart.Document = new Document(new Body(new Paragraph()));
// Create empty style definitions part.
var styleDefinitionsPart = mainDocumentPart.AddNewPart<StyleDefinitionsPart>();
styleDefinitionsPart.Styles = new Styles();
// Create empty numbering definitions part.
var numberingDefinitionsPart = mainDocumentPart.AddNewPart<NumberingDefinitionsPart>();
numberingDefinitionsPart.Numbering = new Numbering();
}
I need to create a same copy of existing word document and open it as another instance while the original first document being opened. The second word document do not save but user may have the option to save it or not.
This need to be done using OpenXML.
I will attached here the current implementation. This implementation is having several issues.
The first document need to close first before use it in WordprocessingDocument using statement.
The second newly created document need to save in local folder.
Code Initiation
var doc = Globals.ThisAddIn.Application.ActiveDocument;
doc.Save();
string fileName = doc.FullName;
doc.Close();
using (WordprocessingDocument document = WordprocessingDocument.Create(fileName, WordprocessingDocumentType.Document))
{
}
Why do you need to use OpenXML ? With Interop you could simply:
Open the existing document
Copy everything within the document range
Create a new document
Paste the other document in the new one
It's done quickly and does the job perfectly
I'm trying to insert a file programmatically (*.zip for example) into an existing docx file.
I looked at the docx open library but it doesn't have the function there.
Also tried using Microsoft.Office.Interop.Word. I created a word document with a table, and I'm trying to insert a file into a cell inside the table.
Word.Application wordApp = new Word.Application();
wordApp.Visible = false;
Word.Document doc = new Word.Document();
doc = wordApp.Documents.Open(Environment.CurrentDirectory + "\\test.docx");
doc.Tables[1].Rows[2].Cells[1].Range.InsertFile((Environment.CurrentDirectory + "\\tttt.zip"));
but it caused an error:
"The file appears to be corrupted"
Can anyone have experience and help with this?
After a lot of trial and error...
The function "Range.InsertFile" doesn't actually inserts a file, it reads and appends the text into the Range.
The solution was simple - Copy paste...
using System.Collections.Specialized;
...
...
//Copy the Filename to Clipboard (setFile function uses StringCollection)
StringCollection collection = new StringCollection();
collection.Add(Environment.CurrentDirectory + "\\MyFile.zip");
Clipboard.SetFileDropList(collection);
//Paste into the selected Range.
range.Paste();
*There is also function "PasteSpecial" which didn't work (Only specific data types are supported).
I am generating collections (Acrobat Portfolios) via iTextSharp. I would like to assign an existing custom navigator (custom layout) to the generated collection. I believe iTextSharp allows for the CUSTOM parameter to define a custom navigator, as in the last code line of this block:
Document document = new Document();
FileStream stream = new FileStream(portfolioPath, FileMode.Create);
PdfWriter writer = PdfWriter.GetInstance(document, stream);
document.Open();
document.Add(new Paragraph(" "));
PdfCollection collection = new PdfCollection(PdfCollection.CUSTOM);
//The integer 3 can also substitute for PdfCollection.CUSTOM
However, when the collection/portfolio is generated the CUSTOM parameter inserts the TILE layout within the generated collection. I want to have the CUSTOM parameter use a custom .nav navigator I developed to insert a custom layout.
I located this post on SO:
How to embed a .nav file into pdf portfolio?
which lead to:
AdobeĀ® Supplement to the
ISO 32000
BaseVersion: 1.7
ExtensionLevel: 3e
Pages 34 - 37 of this document says it is possible to have the collection access the custom navigator by adjusting the Navigator entry in the collection dictionary and the navigator dictionary itself. Additionally, page 541 of the Second Edition of iText in Action implies this is possible (and it is my hope what is possible in iText is also possible in iTextSharp).
So is it possible -- using iTextSharp -- to have a generated collection/portfolio access and implement a custom layout/navigator? If so, how? Or is there another way to do this via C# and/or through some workaround? All help is greatly appreciated.
I found a different way to dictate the custom layout/navigator using iTextSharp. Instead of defining the custom layout during or after
PdfCollection collection = new PdfCollection(PdfCollection.CUSTOM);
I threw out the code listed in my question and used the iTextSharp stamper.
First, I created an empty portfolio file. Within this file I assigned the custom layout that I wanted to use. When opened the file displays the layout, but contains no attached files. This file will serve as a template and assign it's navigator to each newly created iTextSharp PDF using this code:
const string templatePath = #"C:\PortfolioTemplate\PortfolioTemplate.pdf"; //this file will contain the custom navigator/layout for the new pdf
const string portfolioPath = #"C:\OutputFile\NewPortfolio.pdf";
string[] packageitems = { file-to-add-to-collection };
PdfReader reader = new PdfReader(templatePath);
FileStream outputstream = new FileStream(portfolioPath, FileMode.Create);
PdfStamper stamp = new PdfStamper(reader, outputstream);
PdfFileSpecification fs = PdfFileSpecification.FileEmbedded(stamp.Writer, packageitems[0], packageitems[0], null);
stamp.AddFileAttachment(packageitems[0], fs);
stamp.Close();
I used the above proof of concept to loop through all the files in a directory folder and I have created large portfolios without issue that are styled using the custom navigator/layout I wanted.
After coming up with the idea of using a template to pass the navigator into a newly created portfolio, I used the code in the below link to guide me to the above conclusion:
http://itextsharp.10939.n7.nabble.com/Attach-file-td3812.html
Input are Excel files - the cells may contain some basic HTML formatting like <b>, <br>, <h2>.
I want to read the strings and insert the text as formatted text into word documents, i.e. <b>Foo</b> would be shown as a bold string in Word.
I don't know which tags are used so I need a "generic solution", a find/replace approach does not work for me.
I found a solution from January 2011 using the WebBrowser component. So the HTML is converted to RTF and the RTF is inserted into Word. I was wondering if there is a better solution today.
Using a commercial component is fine for me.
Update
I came across Matthew Manela's MarkupConverter class. It converts HTML to RTF. Then I use the clipboard to insert the snippet into the word file
// rtf contains the converted html string using MarkupConverter
Clipboard.SetText(rtf, TextDataFormat.Rtf);
// objTable is a table in my word file
objTable.Cell(1, 1).Range.Paste();
This works, but will copy/pasting up to a few thousand strings using the clipboard break anything?
You will need the OpenXML SDK in order to work with OpenXML. It can be quite tricky getting into, but it is very powerful, and a whole lot more stable and reliable than Office Automation or Interop.
The following will open a document, create an AltChunk part, add the HTML to it, and embed it into the document. For a broader overview of AltChunk see Eric White's blog
using (var wordDoc = WordprocessingDocument.Open("DocumentName.docx", true))
{
var altChunkId = "AltChunkId1";
var mainPart = wordDoc.MainDocumentPart;
var chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.Html, altChunkId);
using (var textStream = new MemoryStream())
{
var html = "<html><body>...</body></html>";
var data = Encoding.UTF8.GetBytes(html);
textStream.Write(data, 0, data.Length);
textStream.Position = 0;
chunk.FeedData(textStream);
}
var altChunk = new AltChunk();
altChunk.Id = altChunkId;
mainPart.Document.Body.InsertAt(altChunk, 0);
mainPart.Document.Save();
}
Obviously for your case, you will want to find (or build) the table you want and insert the AltChunk there instead of at the first position in the body. Note that the HTML that you insert into the word doc must be full HTML documents, with an <html> tag. I'm not sure if <body> is required, but it doesn't hurt. If you just have HTML formatted text, simply wrap the text in these tags and insert into the doc.
It seems that you will need to use Office Automation/Interop to get the table heights. See this answer which says that the OpenXML SDK does not update the heights, only Word does.
Use this code it is working..
Response.AppendHeader("content-disposition", "attachment;filename=FileEName.xls");
Response.Charset = "";
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = "application/vnd.ms-excel";
this.EnableViewState = false;
//Response.Write("Your HTML Code");
Response.Write("<table border='1 px solid'><tr><th>sfsd</th><th>sfsdfssd</th></tr><tr>
<td>ssfsdf</td><td><table border='1 px solid'><tr><th>sdf</th><th>hhsdf</th></tr><tr>
<td>sdfds</td><td>sdhjhfds</td></tr></table></td></tr></table>");
Response.End();
Why not let WORD do its owns translation since it understands HTML.
Read your Excel cells
Write your values into a HTML textfile as it would be a WORD document.
Open WORD and let it read that HTML file.
Instruct WORD to save the document as a new WORD document (if that is required).