Obtain value from mergefield C# Word VSTO - c#

The title says all, but I'll explain more in detail. Here is the thing.
I'm currently developing extra code in a Word VSTO add-in which uses mergefields in a template. The mergefields in the template are filled using data from an external file. What I need is to read the value from a Merge Field but I have totally no clue how to accomplish this. I've been searching for a few days now but none of the articles I read worked for me...
So the question:
How can I get the value from a specific merge field in Word using VSTO?

Mailmerge is quite simple in VSTO, Here is the two magic lines that will do
//Pass in the path of external file
document.MailMerge.OpenDataSource(Name: vm.FilePath.FullName);
document.MailMerge.Destination = WdMailMergeDestination.wdSendToNewDocument;
I found another full example here
This codeblock retrieves all fields in the document
public static List<string> GetFieldsUsedInDocument(Document document)
{
var fields = new List<string>();
foreach (MailMergeField fld in document.MailMerge.Fields)
{
if (fld.Code != null)
{
fields.Add(fld.Code.Text.ToUpper());
}
}
return fields;
}
To get the MergeField names from the list of fields returned above GetFieldsUsedInDocument
public static List<string> GetMergeFields(List<string> allFields)
{
var merges = new List<string>();
foreach (var field in allFields)
{
var isNestedField = false;
foreach (var fieldChar in field)
{
int charCode = fieldChar;
if (charCode == 19 || charCode == 21)
{
isNestedField = true;
break;
}
}
if (!isNestedField)
{
var fieldCode = field;
if (fieldCode.Contains("MERGEFIELD"))
{
var fieldName = fieldCode.Replace("MERGEFIELD", string.Empty).Replace('"', ' ').Trim();
var charsToGet = fieldName.IndexOf(" ");
if (charsToGet < 0)
charsToGet = fieldName.IndexOf(#"\");
charsToGet = charsToGet > 0 ? charsToGet : fieldName.Length;
fieldName = fieldName.Substring(0, charsToGet);
if (!merges.Contains(fieldName))
{
merges.Add(fieldName);
}
}
}
}
return merges;
}

Related

How to replace column data with blank inside C# script

I have been trying to add code to replace column data that has the word "null" to just a blank. I am not proficient in C#, so have no idea where to add it or how to code it. Below is the snippet of code that reads from the text file. I have no clue if the current code supports adding the replacement logic, so I am open to modifying the code to make it happen.
public override void PreExecute()
{
base.PreExecute();
int columnIndex;
string columnName = string.Empty;
string currentFile = Variables.vCurrentInputFile;
string[] currentRowValues;
string delimiter = Variables.vFileDelimiter;
Type fieldDataType;
string messageText;
DataRow row;
int upperBound;
bool skippedColumn = false;
var skippedColumns = new StringBuilder();
_FormatProvider = CultureInfo.GetCultureInfo(ComponentMetaData.LocaleID);
if (BuildImportBuffer())
{
try
{
using (var fileReader = new TextFieldParser(currentFile))
{
fileReader.SetDelimiters(delimiter);
fileReader.TrimWhiteSpace = false;
var fileColumns = fileReader.ReadFields();
while (!fileReader.EndOfData)
{
currentRowValues = fileReader.ReadFields();
if (currentRowValues != null)
{
upperBound = currentRowValues.GetUpperBound(0);
row = _BufferTable.NewRow();
try
{
var loopTo = upperBound;
for (columnIndex = 0; columnIndex <= loopTo; columnIndex++)
{
columnName = fileColumns[columnIndex];
if (!skippedColumn || skippedColumn && !skippedColumns.ToString().Contains(columnName))
{
fieldDataType = _BufferTable.Columns[columnName].DataType;
row[columnName] = Convert.ChangeType(currentRowValues[columnIndex].Trim(), fieldDataType, _FormatProvider);
}
}
}
I really need help with the code since I don't even know how to modify the current logic.

Split string that includes multiline substrings into substrings [duplicate]

I'm writing a simple import application and need to read a CSV file, show result in a DataGrid and show corrupted lines of the CSV file in another grid. For example, show the lines that are shorter than 5 values in another grid. I'm trying to do that like this:
StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
row = line.Split(',');
importingData.Add(new Transaction
{
Date = DateTime.Parse(row[0]),
Reference = row[1],
Description = row[2],
Amount = decimal.Parse(row[3]),
Category = (Category)Enum.Parse(typeof(Category), row[4])
});
}
but it's very difficult to operate on arrays in this case. Is there a better way to split the values?
Don't reinvent the wheel. Take advantage of what's already in .NET BCL.
add a reference to the Microsoft.VisualBasic (yes, it says VisualBasic but it works in C# just as well - remember that at the end it is all just IL)
use the Microsoft.VisualBasic.FileIO.TextFieldParser class to parse CSV file
Here is the sample code:
using (TextFieldParser parser = new TextFieldParser(#"c:\temp\test.csv"))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
//Processing row
string[] fields = parser.ReadFields();
foreach (string field in fields)
{
//TODO: Process field
}
}
}
It works great for me in my C# projects.
Here are some more links/informations:
MSDN: Read From Comma-Delimited Text Files in Visual Basic
MSDN: TextFieldParser Class
I recommend CsvHelper from NuGet.
PS: Regarding other more upvoted answers, I'm sorry but adding a reference to Microsoft.VisualBasic is:
Ugly
Not cross-platform, because it's not available in .NETCore/.NET5 (and Mono never had very good support of Visual Basic, so it may be buggy).
My experience is that there are many different csv formats. Specially how they handle escaping of quotes and delimiters within a field.
These are the variants I have ran into:
quotes are quoted and doubled (excel) i.e. 15" -> field1,"15""",field3
quotes are not changed unless the field is quoted for some other reason. i.e. 15" -> field1,15",fields3
quotes are escaped with \. i.e. 15" -> field1,"15\"",field3
quotes are not changed at all (this is not always possible to parse correctly)
delimiter is quoted (excel). i.e. a,b -> field1,"a,b",field3
delimiter is escaped with \. i.e. a,b -> field1,a\,b,field3
I have tried many of the existing csv parsers but there is not a single one that can handle the variants I have ran into. It is also difficult to find out from the documentation which escaping variants the parsers support.
In my projects I now use either the VB TextFieldParser or a custom splitter.
Sometimes using libraries are cool when you do not want to reinvent the wheel, but in this case one can do the same job with fewer lines of code and easier to read compared to using libraries.
Here is a different approach which I find very easy to use.
In this example, I use StreamReader to read the file
Regex to detect the delimiter from each line(s).
An array to collect the columns from index 0 to n
using (StreamReader reader = new StreamReader(fileName))
{
string line;
while ((line = reader.ReadLine()) != null)
{
//Define pattern
Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");
//Separating columns to array
string[] X = CSVParser.Split(line);
/* Do something with X */
}
}
CSV can get complicated real fast.
Use something robust and well-tested:
FileHelpers:
www.filehelpers.net
The FileHelpers are a free and easy to use .NET library to import/export data from fixed length or delimited records in files, strings or streams.
Another one to this list, Cinchoo ETL - an open source library to read and write CSV files
For a sample CSV file below
Id, Name
1, Tom
2, Mark
Quickly you can load them using library as below
using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
foreach (dynamic item in reader)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
}
}
If you have POCO class matching the CSV file
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
You can use it to load the CSV file as below
using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
foreach (var item in reader)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.Name);
}
}
Please check out articles at CodeProject on how to use it.
Disclaimer: I'm the author of this library
I use this here:
http://www.codeproject.com/KB/database/GenericParser.aspx
Last time I was looking for something like this I found it as an answer to this question.
private static DataTable ConvertCSVtoDataTable(string strFilePath)
{
DataTable dt = new DataTable();
using (StreamReader sr = new StreamReader(strFilePath))
{
string[] headers = sr.ReadLine().Split(',');
foreach (string header in headers)
{
dt.Columns.Add(header);
}
while (!sr.EndOfStream)
{
string[] rows = sr.ReadLine().Split(',');
DataRow dr = dt.NewRow();
for (int i = 0; i < headers.Length; i++)
{
dr[i] = rows[i];
}
dt.Rows.Add(dr);
}
}
return dt;
}
private static void WriteToDb(DataTable dt)
{
string connectionString =
"Data Source=localhost;" +
"Initial Catalog=Northwind;" +
"Integrated Security=SSPI;";
using (SqlConnection con = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#policyID", SqlDbType.Int).Value = 12;
cmd.Parameters.Add("#statecode", SqlDbType.VarChar).Value = "blagh2";
cmd.Parameters.Add("#county", SqlDbType.VarChar).Value = "blagh3";
con.Open();
cmd.ExecuteNonQuery();
}
}
}
Here's a solution I coded up today for a situation where I needed to parse a CSV without relying on external libraries. I haven't tested performance for large files since it wasn't relevant to my particular use case but I'd expect it to perform reasonably well for most situations.
static List<List<string>> ParseCsv(string csv) {
var parsedCsv = new List<List<string>>();
var row = new List<string>();
string field = "";
bool inQuotedField = false;
for (int i = 0; i < csv.Length; i++) {
char current = csv[i];
char next = i == csv.Length - 1 ? ' ' : csv[i + 1];
// if current character is not a quote or comma or carriage return or newline (or not a quote and currently in an a quoted field), just add the character to the current field text
if ((current != '"' && current != ',' && current != '\r' && current != '\n') || (current != '"' && inQuotedField)) {
field += current;
} else if (current == ' ' || current == '\t') {
continue; // ignore whitespace outside a quoted field
} else if (current == '"') {
if (inQuotedField && next == '"') { // quote is escaping a quote within a quoted field
i++; // skip escaping quote
field += current;
} else if (inQuotedField) { // quote signifies the end of a quoted field
row.Add(field);
if (next == ',') {
i++; // skip the comma separator since we've already found the end of the field
}
field = "";
inQuotedField = false;
} else { // quote signifies the beginning of a quoted field
inQuotedField = true;
}
} else if (current == ',') { //
row.Add(field);
field = "";
} else if (current == '\n') {
row.Add(field);
parsedCsv.Add(new List<string>(row));
field = "";
row.Clear();
}
}
return parsedCsv;
}
First of all need to understand what is CSV and how to write it.
Every next string ( /r/n ) is next "table" row.
"Table" cells is separated by some delimiter symbol. Most often used symbols is \t or ,
Every cell possibly can contain this delimiter symbol (cell must to start with quotes symbol and ends with this symbol in this case)
Every cell possibly can contains /r/n sybols (cell must to start with quotes symbol and ends with this symbol in this case)
The easiest way for C#/Visual Basic to work with CSV files is to use standard Microsoft.VisualBasic library. You just need to add needed reference, and the following string to your class:
using Microsoft.VisualBasic.FileIO;
Yes, you can use it in C#, don't worry. This library can read relatively big files and supports all of needed rules, so you will be able to work with all of CSV files.
Some time ago I had wrote simple class for CSV read/write based on this library. Using this simple class you will be able to work with CSV like with 2 dimensions array.
You can find my class by the following link:
https://github.com/ukushu/DataExporter
Simple example of using:
Csv csv = new Csv("\t");//delimiter symbol
csv.FileOpen("c:\\file1.csv");
var row1Cell6Value = csv.Rows[0][5];
csv.AddRow("asdf","asdffffff","5")
csv.FileSave("c:\\file2.csv");
To complete the previous answers, one may need a collection of objects from his CSV File, either parsed by the TextFieldParser or the string.Split method, and then each line converted to an object via Reflection. You obviously first need to define a class that matches the lines of the CSV file.
I used the simple CSV Serializer from Michael Kropat found here: Generic class to CSV (all properties)
and reused his methods to get the fields and properties of the wished class.
I deserialize my CSV file with the following method:
public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
if (!File.Exists(fileFullPath))
{
return null;
}
var list = new List<T>();
var csvFields = GetAllFieldOfClass<T>();
var fieldDict = new Dictionary<int, MemberInfo>();
using (TextFieldParser parser = new TextFieldParser(fileFullPath))
{
parser.SetDelimiters(delimiter);
bool headerParsed = false;
while (!parser.EndOfData)
{
//Processing row
string[] rowFields = parser.ReadFields();
if (!headerParsed)
{
for (int i = 0; i < rowFields.Length; i++)
{
// First row shall be the header!
var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
if (csvField != null)
{
fieldDict.Add(i, csvField);
}
}
headerParsed = true;
}
else
{
T newObj = new T();
for (int i = 0; i < rowFields.Length; i++)
{
var csvFied = fieldDict[i];
var record = rowFields[i];
if (csvFied is FieldInfo)
{
((FieldInfo)csvFied).SetValue(newObj, record);
}
else if (csvFied is PropertyInfo)
{
var pi = (PropertyInfo)csvFied;
pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
}
else
{
throw new Exception("Unhandled case.");
}
}
if (newObj != null)
{
list.Add(newObj);
}
}
}
}
return list;
}
public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
return
from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
select mi;
}
I'd highly suggest using CsvHelper.
Here's a quick example:
public class csvExampleClass
{
public string Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
}
var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );
public static List<T> DeserializeCsvFile<T>(string text)
{
CsvReader csv = new CsvReader( new StringReader( text ) );
csv.Configuration.Delimiter = ",";
csv.Configuration.HeaderValidated = null;
csv.Configuration.MissingFieldFound = null;
return (List<T>)csv.GetRecords<T>();
}
Full documentation can be found at: https://joshclose.github.io/CsvHelper

How to remove a word that already exist in errorList

I have a method that runs spellcheck in a word, but what i want here is that if the word already exist in my errorlist it should not be add as error. below is my code.
public void CheckSpelling()
{
int lineno = 0;
bool start = false;
foreach (string line in _contentList)
{
lineno++;
if (line.Contains("<text>"))
{
start = true;
}
if (start)
{
foreach (Match match in Regex.Matches(line, "<.*?>[^<]+</(.*?)>", RegexOptions.IgnoreCase))
{
List<string> customdiclist = new List<string>(File.ReadAllLines(Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigurationManager.AppSettings["customdic"])));
string[] strArray = Regex.Replace(match.Value, "</?[^>]+>", string.Empty).Split(' ');
foreach (string word in strArray)
{
if ((word.Trim() != string.Empty) && ((word.Substring(0, 1) != word.Substring(0, 1).ToUpper()) && !_helper.CheckSpelling(Regex.Match(word, "[a-zA-Z]+").Value) && !customdiclist.Contains(word)))
{
ErrorModel errorModel = new ErrorModel()
{
LineNumber = lineno,
ErrorMessage = "Please Check Misspelled words",
Text = word
};
ErrorList.Add(errorModel);
}
}
}
}
}
}
_helper.CheckSpelling
class Helper
{
private static readonly string DictPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigurationManager.AppSettings["dictionary"]);
private readonly Hunspell _splCheck = new Hunspell(DictPath + #"\nl_NL.aff", DictPath + #"\nl_NL.dic");
public bool CheckSpelling(string strWord)
{
if(!_splCheck.Spell(strWord.Trim()))
{
return false;
}
return true;
}
}
Can some one help, I dont want any duplicate word in my errorlist.
You can use LINQ to check if your ErrorList already contains an ErrorModel object with a Text property which has the value of the word you are checking.
Using FirstOrDefault you can check to see if it returns a null. If an item cannot be found in the list, a null will be returned as the default value because your list is a collection of class objects (whose default value is null).
If the FirstOrDefault returns null then the word has not been added to the ErrorList.
For more information see When to use .First and when to use .FirstOrDefault with LINQ?.
foreach (string word in strArray)
{
if ((word.Trim() != string.Empty) && ((word.Substring(0, 1) != word.Substring(0, 1).ToUpper()) && !_helper.CheckSpelling(Regex.Match(word, "[a-zA-Z]+").Value) && !customdiclist.Contains(word)))
{
if (ErrorList.FirstOrDefault(e => e.Text == word) == null)
{
ErrorModel errorModel = new ErrorModel()
{
LineNumber = lineno,
ErrorMessage = "Please Check Misspelled words",
Text = word
};
ErrorList.Add(errorModel);
}
}
}

Duplicate fields when generating PDF from template

I am reading an XML file and using the data to fill out a PDF template.
The XML file has a field called <my:SpecialInstructions>Test for special instructions</my:SpecialInstructions>
whereas the PDF template fields are:
and the fields are called:
The problem I am getting is Test for special instructions is getting duplicated once for the first line and once for the second line
question: how does the <my:SpecialInstructions> tag get translated into my:SpecialInstructions1 & my:SpecialInstructions2?
I am using XMLParse, c# for this.
Per Robert's suggestion I am including the code that is populating the PDF:
public void InsertDataToTemplate(DataTable dt)
{
// cycle through datatable and find field to field matches
DataRow row = dt.Rows[0];
int fieldType = 0;
string checkBoxInsert = "";
List<string> notFoundList = new List<string>();
foreach (DataColumn col in dt.Columns)
{
if (pdfStamper.AcroFields.Fields.Where(afd => afd.Key == col.ColumnName).Count() != 0)
{
fieldType = pdfStamper.AcroFields.GetFieldType(col.ColumnName);
if (fieldType == AcroFields.FIELD_TYPE_CHECKBOX)
{
checkBoxInsert = (row[col.ColumnName].ToString().ToUpper() == "FALSE") ? "NO" : "Yes";
pdfStamper.AcroFields.SetField(col.ColumnName, checkBoxInsert);
}
else
{
pdfStamper.AcroFields.SetField(col.ColumnName, row[col.ColumnName].ToString());
}
}
else
notFoundList.Add(col.ColumnName);
}
}

Display values in Excel Spreadhseet rather than to console, Linq to xml, c#

I have this working code that parses values from XML files. Instead of writing the data to the console, how can I write data to an Excel spreadsheet? Any help please.
namespace TestCFG
{
class Program
{
public class XAxisCalib
{
public int Max1 { get; set; }
public int Min2 { get; set; }
public int Max3 { get; set; }
public int Min4 { get; set; }
public int Max5 { get; set; }
public int Min6 { get; set; }
}
static void Main(string[] args)
{
string[] fileEntries = Directory.GetFiles(#"c:\Sciclone UAC", "*.cfg*");
foreach (string fileName in fileEntries)
{
XDocument doc = XDocument.Load(fileName);
var query = from x in doc.Descendants("XAxisCalib")
select new
{
//Max1 = x.Attribute("Max").Value,
//Min2 = x.Attribute("Min").Value
MaxChild = x.Descendants("Max"),
MinChild = x.Descendants("Min")
};
foreach (var x in query)
{
foreach (var nextLevel in x.MaxChild)
{
Console.WriteLine("XMax: " + nextLevel.Value);
}
foreach (var nextLevel in x.MinChild)
{
Console.WriteLine("XMin: " + nextLevel.Value);
}
//Console.WriteLine("XAxisCalib");
}
var query2 = from y in doc.Descendants("YAxisCalib")
select new
{
//Max3 = x.Attribute("Max").Value,
//Min4 = x.Attribute("Min").Value
MaxChild = y.Descendants("Max"),
MinChild = y.Descendants("Min")
};
foreach (var y in query2)
{
foreach (var nextLevel in y.MaxChild)
{
Console.WriteLine("YMax: " + nextLevel.Value);
}
foreach (var nextLevel in y.MinChild)
{
Console.WriteLine("YMin: " + nextLevel.Value);
}
//Console.WriteLine("YAxisCalib");
var query3 = from z in doc.Descendants("ZAxisCalib")
select new
{
//Max5 = x.Attribute("Max").Value,
//Min6 = x.Attribute("Min").Value
MaxChild = z.Descendants("Max"),
MinChild = z.Descendants("Min")
};
foreach (var z in query3)
{
foreach (var nextLevel in z.MaxChild)
{
Console.WriteLine("ZMax: " + nextLevel.Value);
}
foreach (var nextLevel in z.MinChild)
{
Console.WriteLine("ZMin: " + nextLevel.Value);
}
//Console.WriteLine("ZAxisCalib");
}
}
}
}
}
}
Use the office API for that
something like
using System;
using System.Reflection;
using Microsoft.Office.Interop.Excel;
public class CreateExcelWorksheet
{
static void Main()
{
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
if (xlApp == null)
{
Console.WriteLine("EXCEL could not be started. Check that your office installation and project references are correct.");
return;
}
xlApp.Visible = true;
Workbook wb = xlApp.Workbooks.Add(XlWBATemplate.xlWBATWorksheet);
Worksheet ws = (Worksheet)wb.Worksheets[1];
if (ws == null)
{
Console.WriteLine("Worksheet could not be created. Check that your office installation and project references are correct.");
}
// Select the Excel cells, in the range c1 to c7 in the worksheet.
Range aRange = ws.get_Range("C1", "C7");
if (aRange == null)
{
Console.WriteLine("Could not get a range. Check to be sure you have the correct versions of the office DLLs.");
}
// Fill the cells in the C1 to C7 range of the worksheet with the number 6.
Object[] args = new Object[1];
args[0] = 6;
aRange.GetType().InvokeMember("Value", BindingFlags.SetProperty, null, aRange, args);
// Change the cells in the C1 to C7 range of the worksheet to the number 8.
aRange.Value2 = 8;
}
}
From
There are several ways to create a spreadsheet from XML.
- Use the Office API. This is a good, if heavy, approach. The API is very complex, overkill for simple operations, but necessary if you need formulas.
- Write out Excel XML, even more complex.
- Write out a CSV file, good for simple, non-formatted output. Watch-out for values with commas, etc.
- Write out an HTML table, Excel will open everything in cells.
Using Json.NET, I believe you can do something like this:
string json = JsonConvert.SerializeObject(table, Formatting.Indented);
Here's a link to Json.NET:
http://json.codeplex.com/

Categories

Resources