I am creating a mail merge document using OpenXML dll. I have a requirement to add a dynamic table to the word document. Currently I have been able to add the table # the end of the document but I need to add it some where in the middle of the page.
I have 4 pages in the word document and this table has to be added to the start of the 3rd page. I have been able to get the table. The only issue that I have is to add the table here.
The following is the code:
void createTemplate(string newFileName,string folderName,ArrayList mailMergeList,DataTable observations)
{
FileInfo newFile = new FileInfo(newFileName);
if (!IsFileLocked(newFile))
{
//declare and open a Word document object
WordprocessingDocument objWordDocx = WordprocessingDocument.Open(newFileName, true);
//get the main document section of the document
OpenXmlElement objMainDoc = objWordDocx.MainDocumentPart.Document;
//var wordDoc = new Microsoft.Office.Interop.Word.Document();
//Loop through merge fields
string FieldDelimiter = " MERGEFIELD ";
foreach (FieldCode field in objWordDocx.MainDocumentPart.RootElement.Descendants<FieldCode>())
{
var fieldNameStart = field.Text.LastIndexOf(FieldDelimiter, System.StringComparison.Ordinal);
String fieldname = field.Text.Substring(fieldNameStart + FieldDelimiter.Length).Trim();
fieldname = fieldname.Substring(0, fieldname.IndexOf(' '));
// fieldname
var fieldValue = "";
fieldValue = GetMergeValue(fieldname, mailMergeList);
// Go through all of the Run elements and replace the Text Elements Text Property
foreach (Run run in objWordDocx.MainDocumentPart.Document.Descendants<Run>())
{
foreach (Text txtFromRun in run.Descendants<Text>().Where(a => a.Text == "«" + fieldname + "»"))
{
if (fieldname.Equals("ObservationsTable"))
{
//observations
if (observations.Rows.Count > 0) //only if there is data in the Resi Obs NOI sheet we need to create a table
{
txtFromRun.Text = CreateTable(objWordDocx, newFileName, observations).ToString();
}
}
else
{
txtFromRun.Text = GetMergeValue(fieldname, mailMergeList);
}
}
}
}
//save this part
objWordDocx.MainDocumentPart.Document.Save();
//save and close the document
objWordDocx.Close();
}
}
I have been given a solution below but it is not feasible for me as I am not using Word.Interop dll.
Please guide.
Here's an open xml example. I created a dummy table:
var tab = new Table();
for (var z = 0; z < 2; z++)
{
var tr = new TableRow();
for (var j = 0; j < 2; j++)
{
var tc = new TableCell();
tc.Append(new Paragraph(new Run(new Text("i: " + z + " j:" + j))));
tr.Append(tc);
}
tab.Append(tr);
}
In my word.docx I have:
Some text
«Table»
some other text
And to loop over the merge fields:
WordprocessingDocument objWordDocx = WordprocessingDocument.Open(newFileName, true);
OpenXmlElement objMainDoc = objWordDocx.MainDocumentPart.Document;
foreach (var field in objMainDoc.Descendants<SimpleField>())
{
if (field.Instruction.Value.Trim().EndsWith("Table"))
{
var tabRun = new Run(tab);
field.Parent.ReplaceChild<SimpleField>(tabRun, field);
}
}
objWordDocx.MainDocumentPart.Document.Save();
objWordDocx.Close();
EDIT:
Version with FieldCode:
foreach (var field in objMainDoc.Descendants<FieldCode>())
{
if (field.InnerText.Trim().EndsWith("Table"))
{
var tabRun = new Run(tab);
var anc = field.Ancestors<Paragraph>().FirstOrDefault();
anc.RemoveAllChildren();
anc.Append(tabRun);
}
}
Note: this works for me as the only thing in my paragrah is the field code. If you have stuff in your paragraph which shouldn't be removed, modify the code.
In your document (wordDoc below) add a mergefield, "CustomTable" for example.
Object oMissing = System.Reflection.Missing.Value;
Object oTemplatePath = templatePath; // Path
var wordApp = new Microsoft.Office.Interop.Word.Application();
var wordDoc = new Microsoft.Office.Interop.Word.Document();
wordDoc = wordApp.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);
foreach (Field field in wordDoc.Fields)
{
var fieldText = field.Code.Text;
var fieldName = fieldText.Substring(11).Split(new string[] { "\\" }, StringSplitOptions.None)[0].Trim();
field.Select();
if (fieldText.StartsWith(" MERGEFIELD"))
{
if (fieldName == "CustomTable")
{
var tab = wordDoc.Tables.Add(wordApp.Selection.Range, noOfColumns, noOfRows);
tab.Cell(1, 1).Range.Text = "Some text";
// ETC
}
}
}
Related
I have a template word file in SQL server database database
that i need to change sum texts on it.
I try to download the file first to temp files and then I load it to my code.
is there is a method to load the file directly to my code I using this (saving file to drive first)
after that I need to change image with other image in DB.
public Boolean save_agaza_file(string pattth)
{
Boolean result = false;
Document wordDoc = new Document();
Application wordApp = new Application();
try
{
//OBJECT OF MISSING "NULL VALUE"
Object oMissing = System.Reflection.Missing.Value;
Object oTemplatePath = pattth;
//wordDoc = new Document();
wordDoc = wordApp.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);
DateTime day_start = DateTime.Now.AddDays(1);
DateTime day_end = DateTime.Now.AddDays(2);
foreach (Range rr in wordDoc.StoryRanges)
{
}
foreach (Field myMergeField in wordDoc.Fields)
{
Range rngFieldCode = myMergeField.Code;
String fieldText = rngFieldCode.Text;
// ONLY GETTING THE MAILMERGE FIELDS
if (fieldText.StartsWith(" MERGEFIELD"))
{
// THE TEXT COMES IN THE FORMAT OF
// MERGEFIELD MyFieldName \\* MERGEFORMAT
// THIS HAS TO BE EDITED TO GET ONLY THE FIELDNAME "MyFieldName"
Int32 endMerge = fieldText.IndexOf("\\");
Int32 fieldNameLength = fieldText.Length - endMerge;
String fieldName = fieldText.Substring(11, endMerge - 11);
// GIVES THE FIELDNAMES AS THE USER HAD ENTERED IN .dot FILE
fieldName = fieldName.Trim();
// **** FIELD REPLACEMENT IMPLEMENTATION GOES HERE ****//
// THE PROGRAMMER CAN HAVE HIS OWN IMPLEMENTATIONS HERE
if (fieldName == "EmpName")
{
myMergeField.Select();
// wordApp.Selection.TypeText("محمد السيد زكى");
wordApp.Selection.TypeText(this.Reqested_emp.Name);
}
}
}
}
catch (Exception e)
{
MessageBox.show(e.Message);
}
}
method to download
string sql = #" select * from Agaza_template where template_id=" + agaza_id + ";";
if (Form1.conn.State != ConnectionState.Open)
{
Form1.conn.Open();
SqlCommand command = new SqlCommand(sql, Form1.conn);
SqlDataReader reader = command.ExecuteReader();
// reader.Read();
//if (reader.HasRows)
if (reader.Read())
{
byte[] b = (byte[])reader[2];
// string s = Path.GetFileName(reader[1].ToString());
string s = reader[1].ToString();
string result = Path.GetTempPath();
file_temp_name = result + s;
FileStream fs = new FileStream(result + s, FileMode.Create);
fs.Write(b, 0, b.Length);
fs.Close();
}
}
Form1.conn.Close();
}
I'm working on generating the complex report from my WPF application in word format. Some of the content, I need to insert in the report is of RTF type. I'm not able to figure out the way to insert the RTF content at the end of particular text range.
As of now, I only know one way of adding the RTF content to word document programmatically and that is by setting the RTF content in clipboard and then paste the data back. But I'm not able to find the way to paste the content exactly after the existing content of text range.
I tried to add the paragraph at the end of text range and paste my RTF content to it but after looping through multiple paste operation final content I see in document is jumbled up and not in correct order.
Following is the code snippet I have tried :
public partial class MainWindow : System.Windows.Window
{
private List<TestData> TestDatas = new List<TestData>();
public MainWindow()
{
InitializeComponent();
TestDatas.Add(new TestData() { Title = "Phrase 01", Description = #"Any RTF Text" });
TestDatas.Add(new TestData() { Title = "Phrase 02", Description = #"Any RTF Text" });
TestDatas.Add(new TestData() { Title = "Phrase 03", Description = #"Any RTF Text" });
TestDatas.Add(new TestData() { Title = "Phrase 04", Description = #"Any RTF Text" });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Object oMissing = System.Reflection.Missing.Value;
var templateFilePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Report Template\"));
Object oTemplatePath = templateFilePath + #"phrases.dotx";
Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
Document wordDoc = new Document();
wordDoc = wordApp.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);
Selection sel = null;
foreach (Field myMergeField in wordDoc.Fields)
{
myMergeField.Select();
sel = wordApp.Selection;
sel.Delete();
Range initialRange = sel.Range;
foreach (var data in TestDatas)
{
Range appendedRange;
var textRange = AppendToRange(initialRange, data.Title + Environment.NewLine, out appendedRange, wordDoc);
appendedRange.Bold = 1;
appendedRange.Font.Size = 11;
appendedRange.Underline = WdUnderline.wdUnderlineSingle;
var para = textRange.Paragraphs.Add();
}
}
wordDoc.SaveAs("myfile.doc");
wordApp.Application.Quit();
}
private Range AppendToRange(Range range, string appendText, out Range appendedRange, Document wordDoc)
{
// Fetch indexes
object oldStartPosition = range.Start;
object oldEndPosition = range.End;
object newEndPosition = (int)oldEndPosition + appendText.Length;
// Append the text
range.InsertAfter(appendText);
// Define the range of the appended text
appendedRange = wordDoc.Range(ref oldEndPosition, ref newEndPosition);
// Return the range of the new combined range
return wordDoc.Range(ref oldStartPosition, ref newEndPosition);
}
}
public class TestData
{
public string Title { get; set; }
public string Description { get; set; }
}
Is there a way to insert RTF content properly at particular location in text range?
Try working with a Range object and "collapse" the Range as you go. I haven't tested the following, just off the top of my head...
var para = textRange.Paragraphs.Add();
var rng = para.Range;
Clipboard.SetData(System.Windows.DataFormats.Rtf, text);
rng.PasteSpecial(DataType: Microsoft.Office.Interop.Word.WdPasteDataType.wdPasteRTF);
rng.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
//Alternative to insert a new paragraph
rng.Text = "\n";
//prepare for the next entry, for illustration - of course you'd built this into a loop
rng.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
Clipboard.SetData(System.Windows.DataFormats.Rtf, otherText);
rng.PasteSpecial(DataType: Microsoft.Office.Interop.Word.WdPasteDataType.wdPasteRTF);
I want to get all the headings along their sub-headings separately from a word file programmatically Using c# for example i have following content :
HEADING 1 XYZ
heading 2 heading 3
HEADING 1 ABC
HEADING 1 DEF
heading 2 lorem ispum
so my code should return me:
Heading 1 XYZ
heading 2 heading 3
seperately and similarly remaining headings and subheadings also.
I have tried this but my code returns me all the headings and subheadings together not seperately , here's my code for getting the headings:
foreach (Microsoft.Office.Interop.Word.Paragraph paragraph in oMyDoc.Paragraphs )
{
Microsoft.Office.Interop.Word.Style style =
paragraph.get_Style() as Microsoft.Office.Interop.Word.Style;
string styleName = style.NameLocal;
string text = paragraph.Range.Text;
if (styleName == "Title")
{
title = text.ToString();
}
else if (styleName == "Subtitle")
{
st = text.ToString() + "\n";
}
else if (styleName=="Heading 1")
{
heading1[h1c] = text.ToString()+"\n";
}
}
I assumed that you have title and st declared as strings, every iteration through the loop the old values are being replaced with the current values. If you use a list, you can add the text and subtitle to them. You can then easily do what you want with them.
List<String> title = new List<String>();
List<String> st = new List<String>();
foreach (Microsoft.Office.Interop.Word.Paragraph paragraph in oMyDoc.Paragraphs )
{
Microsoft.Office.Interop.Word.Style style = paragraph.get_Style() as Microsoft.Office.Interop.Word.Style;
string styleName = style.NameLocal;
string text = paragraph.Range.Text;
if (styleName == "Title")
{
title.Add(text.ToString());
}
else if (styleName == "Subtitle")
{
st.Add(text.ToString());
}
else if (styleName=="Heading 1")
{
heading1[h1c] = text.ToString()+"\n";
}
}
"Heading" is not really reliable if you want to get the whole outline (like what's in the Table of Content), because all the styles can be renamed / copied. So some other styles like "H1" or "标题1"(Chinese word for "Heading1") can act as heading and appear in Table of Content and Navigation Panel.
I even saw "Normal" acting as headings in a doc. That made me give up using styles to find headings.
Try paragraph.OutlineLevel instead. Its value ranges from WdOutlineLevel.wdOutlineLevel1 to 9 (means it's some kind of "heading"), and ends with WdOutlineLevel.wdOutlineLevelBodyText (means it's just text body).
Here is my code. I even built a tree-like heading list (one heading each line).
public static class WordBridge
{
public static Dictionary<WdOutlineLevel, string> Level2Spaces = new Dictionary<WdOutlineLevel, string>()
{
{WdOutlineLevel.wdOutlineLevel1, ""},
{WdOutlineLevel.wdOutlineLevel2, " "},
{WdOutlineLevel.wdOutlineLevel3, " "},
{WdOutlineLevel.wdOutlineLevel4, " "},
{WdOutlineLevel.wdOutlineLevel5, " "},
{WdOutlineLevel.wdOutlineLevel6, " "},
{WdOutlineLevel.wdOutlineLevel7, " "},
{WdOutlineLevel.wdOutlineLevel8, " "},
{WdOutlineLevel.wdOutlineLevel9, " "},
{WdOutlineLevel.wdOutlineLevelBodyText, " "},
};
public static string GetOutlines(object? sender, Document currentWordDoc)
{
var sb = new StringBuilder();
var countFinished = 0;
foreach (Paragraph paragraph in currentWordDoc.Paragraphs)
{
countFinished++;
(sender as BackgroundWorker)?.ReportProgress(countFinished);
if (paragraph.OutlineLevel == WdOutlineLevel.wdOutlineLevelBodyText)
continue;
if (Level2Spaces.ContainsKey(paragraph.OutlineLevel))
sb.Append(Level2Spaces[paragraph.OutlineLevel] + paragraph.Range.Text);
}
return sb.ToString();
}
}
I might make it a real tree at near future.
Btw, remove the ReportProgress things if you do not have a progress bar. But it's really slow because of single thread.
//This will return you headers and text below of corrousponding header
private List<Tuple<string, string>> GetPlainTextByHeaderFromWordDoc(string docname)
{
#region for Plain text collection from document
List<Tuple<string, string>> docPlainTextWithHeaderList = new List<Tuple<string, string>>();
string headerText = string.Empty;
string finalTextBelowHeader = string.Empty;
try
{
Document doc = ReadMsWord(docname, objCommonVariables);
if (doc.Paragraphs.Count > 0)
{
//heading with 1st paragraph
foreach (Paragraph paragraph in doc.Paragraphs)
{
Style style = paragraph.get_Style() as Style;
headerText = string.Empty;
finalTextBelowHeader = string.Empty;
if (style.NameLocal == "Heading 1")
{
headerText = paragraph.Range.Text.TrimStart().TrimEnd();
//reading 1st paragraph of each section
for (int i = 0; i < doc.Paragraphs.Count; i++)
{
if (paragraph.Next(i) != null)
{
Style yle = paragraph.Next(i).get_Style() as Style;
if (yle.NameLocal != "Heading 1")
{
finalTextBelowHeader += paragraph.Next(i).Range.Text.ToString();
}
else if (yle.NameLocal == "Heading 1" && !headerText.Contains(paragraph.Next(i).Range.Text.ToString()))
{
break;
}
}
}
string header = Regex.Replace(headerText, "[^a-zA-Z\\s]", string.Empty).TrimStart().TrimEnd();
string belowText = Regex.Replace(finalTextBelowHeader, #"\s+", String.Empty);
belowText = belowText.Trim().Replace("\a", string.Empty);
docPlainTextWithHeaderList.Add(new Tuple<string, string>(header, belowText));
}
}
}
else
{
//error msg: unable to read
}
doc.Close(Type.Missing, Type.Missing, Type.Missing);
}
catch (Exception ex)
{
MessageBox.Show(ex.StackTrace);
}
}
//This will read and return word document
private Document ReadMsWord(string docName)
{
Document docs = new Document();
try
{
// variable to store file path
string FilePath = #"C:\Kaustubh_Tupe\WordRepository/docName.docx";
// create word application
Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application();
// create object of missing value
object miss = System.Reflection.Missing.Value;
// create object of selected file path
object path = FilePath;
// set file path mode
object readOnly = false;
// open Destination
docs = word.Documents.Open(ref path, ref miss, ref readOnly,
ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss,
ref miss, ref miss, ref miss, ref miss, ref miss);
//select whole data from active window Destination
docs.ActiveWindow.Selection.WholeStory();
// handover the data to cllipboard
docs.ActiveWindow.Selection.Copy();
// clipboard create reference of idataobject interface which transfer the data
}
catch (Exception ex)
{
//MessageBox.Show(ex.ToString());
}
return docs;
}
Using Data tab in MS Excel, I am able to perform "Text to Columns". how to do that using c# code?
Based on this example:
var names = new[]
{
"Brady, Tom",
"Manning, Peyton",
"Peterson, Adrian",
"Lewis, Ray",
"Reed, Ed",
"Polamalu, Troy",
"Johnson, Andre",
"Revis, Darrelle",
"Brees, Drew",
"Peppers, Julius"
};
// Write names to a file
using (var excelPackage = new ExcelPackage(new FileInfo(#"d:\tmp\TextToColumns.xlsx")))
{
var worksheet = excelPackage.Workbook.Worksheets.Add("TextToColumns");
for (int i = 1; i < names.Length; i++)
{
worksheet.Cells[String.Format("A{0}", i)].Value = names[i - 1];
}
excelPackage.Save();
}
// Split names
using (var excelPackage = new ExcelPackage(new FileInfo(#"d:\tmp\TextToColumns.xlsx")))
{
var worksheet = excelPackage.Workbook.Worksheets.First();
foreach (var cell in worksheet.Cells)
{
var splittedValues = ((String)cell.Value).Split(',');
// Write last name to the first column
cell.Value = splittedValues[0];
// Write first name to the next one column
worksheet.Cells[cell.Start.Row, cell.Start.Column + 1].Value = splittedValues[1].TrimStart();
}
excelPackage.Save();
}
I have a class that build the content for my table of contents and that works, fine and dandy.
Here's the code:
public void Build(string center,IDictionary<string,iTextSharp.text.Image> images)
{
iTextSharp.text.Image image = null;
XPathDocument rapportTekst = new XPathDocument(Application.StartupPath + #"..\..\..\RapportTemplates\RapportTekst.xml");
XPathNavigator nav = rapportTekst.CreateNavigator();
XPathNodeIterator iter;
iter = nav.Select("//dokument/brevhoved");
iter.MoveNext();
var logo = iTextSharp.text.Image.GetInstance(iter.Current.GetAttribute("url", ""));
iter = nav.Select("//dokument/gem_som");
iter.MoveNext();
string outputPath = iter.Current.GetAttribute("url", "")+center+".pdf";
iter = nav.Select("//dokument/titel");
iter.MoveNext();
this.titel = center;
Document document = new Document(PageSize.A4, 30, 30, 100, 30);
var outputStream = new FileStream(outputPath, FileMode.Create);
var pdfWriter = PdfWriter.GetInstance(document, outputStream);
pdfWriter.SetLinearPageMode();
var pageEventHandler = new PageEventHandler();
pageEventHandler.ImageHeader = logo;
pdfWriter.PageEvent = pageEventHandler;
DateTime timeOfReport = DateTime.Now.AddMonths(-1);
pageWidth = document.PageSize.Width - (document.LeftMargin + document.RightMargin);
pageHight = document.PageSize.Height - (document.TopMargin + document.BottomMargin);
document.Open();
var title = new Paragraph(titel, titleFont);
title.Alignment = Element.ALIGN_CENTER;
document.Add(title);
List<TableOfContentsEntry> _contentsTable = new List<TableOfContentsEntry>();
nav.MoveToRoot();
iter = nav.Select("//dokument/indhold/*");
Chapter chapter = null;
int chapterCount = 1;
while (iter.MoveNext())
{
_contentsTable.Add(new TableOfContentsEntry("Test", pdfWriter.CurrentPageNumber.ToString()));
XPathNodeIterator innerIter = iter.Current.SelectChildren(XPathNodeType.All);
chapter = new Chapter("test", chapterCount);
while(innerIter.MoveNext())
{
if (innerIter.Current.Name.ToString().ToLower().Equals("billede"))
{
image = images[innerIter.Current.GetAttribute("navn", "")];
image.Alignment = Image.ALIGN_CENTER;
image.ScaleToFit(pageWidth, pageHight);
chapter.Add(image);
}
if (innerIter.Current.Name.ToString().ToLower().Equals("sektion"))
{
string line = "";
var afsnit = new Paragraph();
line += (innerIter.Current.GetAttribute("id", "") + " ");
innerIter.Current.MoveToFirstChild();
line += innerIter.Current.Value;
afsnit.Add(line);
innerIter.Current.MoveToNext();
afsnit.Add(innerIter.Current.Value);
chapter.Add(afsnit);
}
}
chapterCount++;
document.Add(chapter);
}
document = CreateTableOfContents(document, pdfWriter, _contentsTable);
document.Close();
}
I'm then calling the method CreateTableOfContents(), and as such it is doing what it is supposed to do. Here's the code for the method:
public Document CreateTableOfContents(Document _doc, PdfWriter _pdfWriter, List<TableOfContentsEntry> _contentsTable)
{
_doc.NewPage();
_doc.Add(new Paragraph("Table of Contents", FontFactory.GetFont("Arial", 18, Font.BOLD)));
_doc.Add(new Chunk(Environment.NewLine));
PdfPTable _pdfContentsTable = new PdfPTable(2);
foreach (TableOfContentsEntry content in _contentsTable)
{
PdfPCell nameCell = new PdfPCell(_pdfContentsTable);
nameCell.Border = Rectangle.NO_BORDER;
nameCell.Padding = 6f;
nameCell.Phrase = new Phrase(content.Title);
_pdfContentsTable.AddCell(nameCell);
PdfPCell pageCell = new PdfPCell(_pdfContentsTable);
pageCell.Border = Rectangle.NO_BORDER;
pageCell.Padding = 6f;
pageCell.Phrase = new Phrase(content.Page);
_pdfContentsTable.AddCell(pageCell);
}
_doc.Add(_pdfContentsTable);
_doc.Add(new Chunk(Environment.NewLine));
/** Reorder pages so that TOC will will be the second page in the doc
* right after the title page**/
int toc = _pdfWriter.PageNumber - 1;
int total = _pdfWriter.ReorderPages(null);
int[] order = new int[total];
for (int i = 0; i < total; i++)
{
if (i == 0)
{
order[i] = 1;
}
else if (i == 1)
{
order[i] = toc;
}
else
{
order[i] = i;
}
}
_pdfWriter.ReorderPages(order);
return _doc;
}
The problem is however. I want to insert a page break before the table of contents, for the sake of reordering the pages, so that the table of contents is the first page, naturally. But the output of the pdf-file is not right.
Here's a picture of what it looks like:
It seems like the _doc.NewPage() in the CreateTableOfContents() method does not execute correctly. Meaning that the image and the table of contents is still on the same page when the method starts the reordering of pages.
EDIT: To clarify the above, the _doc.NewPage() gets executed, but the blank page is added after the picture and the table of contents.
I've read a couple of places that this could be because one is trying to insert a new page after an already blank page. But this is not the case.
I'll just link to the pdf files aswell, to better illustrate the problem.
The pdf with table of contents: with table of contents
The pdf without table of contents: without table of contents
Thank you in advance for your help :)