Openxml in C# updating only the first MERGEFIELD in a paragraph - c#

I have approximately 10 MERGEFIELD in a document that I'm trying to replace the Text with some value. Here's the code.
using (WordprocessingDocument document = WordprocessingDocument.Open(destinationFileName, true))
{
document.ChangeDocumentType(DocumentFormat.OpenXml.WordprocessingDocumentType.Document);
MainDocumentPart docPart = document.MainDocumentPart;
docPart.AddExternalRelationship("http://schemas.openxmlformats.org/officeDocument/2006/relationships/attachedTemplate", new Uri(destinationFileName, UriKind.RelativeOrAbsolute));
docPart.Document.Save();
IEnumerable<FieldChar> fldChars = document.MainDocumentPart.RootElement.Descendants<FieldChar>();
if (fldChars == null) { return; }
string fieldList = string.Empty;
FieldChar fldCharStart = null;
FieldChar fldCharEnd = null;
FieldChar fldCharSep = null;
FieldCode fldCode = null;
string fldContent = String.Empty;
int i = 1;
foreach(var fldChar in fldChars)
{
System.Diagnostics.Debug.WriteLine(i + ": " + fldChar);
i++;
string fldCharPart = fldChar.FieldCharType.ToString();
System.Diagnostics.Debug.WriteLine("Field Char Length: " + fldChar.Count());
System.Diagnostics.Debug.WriteLine("Field Char part: " + fldCharPart);
switch(fldCharPart)
{
case "begin": // start of the field
fldCharStart = fldChar;
System.Diagnostics.Debug.WriteLine("Field Char Start: " + fldCharStart);
// get the field code, which will be an instrText element
// either as sibling or as a child of the parent sibling
fldCode = fldCharStart.Parent.Descendants<FieldCode>().FirstOrDefault();
System.Diagnostics.Debug.WriteLine("Field Code: " + fldCode);
if (fldCode == null)
{
fldCode = fldCharStart.Parent.NextSibling<Run>().Descendants<FieldCode>().FirstOrDefault();
System.Diagnostics.Debug.WriteLine("New Field Code: " + fldCode);
}
if (fldCode != null && fldCode.InnerText.Contains("MERGEFIELD"))
{
fldContent = getFieldValue(query, prescriber, fldCode.InnerText);
fieldList += fldContent + "\n";
System.Diagnostics.Debug.WriteLine("Field content: " + fldContent);
}
break;
case "end": // end of the field
fldCharEnd = fldChar;
System.Diagnostics.Debug.WriteLine("Field char end: " + fldCharEnd);
break;
case "separate": // complex field with text result
fldCharSep = fldChar;
break;
default:
break;
}
if((fldCharStart != null) && (fldCharEnd != null))
{
if(fldCharSep != null)
{
Text elemText = (Text)fldCharSep.Parent.NextSibling().Descendants<Text>().FirstOrDefault();
elemText.Text = fldContent;
System.Diagnostics.Debug.WriteLine("Element text: " + elemText);
// Delete all field chas with their runs
DeleteFieldChar(fldCharStart);
DeleteFieldChar(fldCharEnd);
DeleteFieldChar(fldCharSep);
fldCode.Remove();
}
else
{
Text elemText = new Text(fldContent);
fldCode.Parent.Append(elemText);
fldCode.Remove();
System.Diagnostics.Debug.WriteLine("Element Text !sep: " + elemText);
DeleteFieldChar(fldCharStart);
DeleteFieldChar(fldCharEnd);
DeleteFieldChar(fldCharSep);
}
fldCharStart = null;
fldCharEnd = null;
fldCharSep = null;
fldCode = null;
fldContent = string.Empty;
}
}
System.Diagnostics.Debug.WriteLine("Field list: " + fieldList);
}
It works to some extent. The problem is when there are more than one field in a paragraph. I have about 4 merge fields in one paragraph in this document, and one field in each paragraph after that. Only the first merge field in the paragraph is being updated and the rest fields in the paragraphs is untouched. Then, it moves to the next paragraph and looks for the field. How can I fix this?

Looks like you are over complicating a simple Mailmerge replacement. Instead of looping through paragraphs you could rather get all mailmerge fields within a document and replace them.
private const string FieldDelimeter = " MERGEFIELD ";
foreach (FieldCode field in doc.MainDocumentPart.RootElement.Descendants<FieldCode>())
{
var fieldNameStart = field.Text.LastIndexOf(FieldDelimeter, System.StringComparison.Ordinal);
var fieldName = field.Text.Substring(fieldNameStart + FieldDelimeter.Length).Trim();
foreach (Run run in doc.MainDocumentPart.Document.Descendants<Run>())
{
foreach (Text txtFromRun in run.Descendants<Text>().Where(a => a.Text == "«" + fieldName + "»"))
{
txtFromRun.Text = "Replace what the merge field here";
}
}
}
doc.MainDocumentPart.Document.Save();
doc is of type WordprocessingDocument.
This will replace all merge fields regardless of the amount of fields in a paragraph.

Related

How can tell the difference between the link and the footnote using C# ITextSharp?

I have a simple PDF file that has the Link and the Footnote on a page. When I run the annotation check using iTextSharp, both of the links return the sub type as Link. Is there a way to tell the difference between those two items?
I did examine through an intellisence the structure of an annotation dictionary and notice the annotation object dictionary for the Footnote has once extra item, which is named "F", and that item has a value of 4. That item is NOT present at all in the annotation dictionary for the Link. Can I use the 'F' item/parameter as a way to tell the difference between the Link and the Footnote?
Thank you very much in advance
Here is the PDF file
Here is our code
public string AnyPDFCheckComments(string inFileName, string strUsername, string strFilename)
{
string strCommentType = string.Empty;
string strWidgetFound = string.Empty;
string strPageNumber = string.Empty;
string message = string.Empty;
string strComments = string.Empty;
string strCommentsFound = string.Empty;
int intCommentCount = 0;
PdfReader reader = new PdfReader(inFileName);
for (int i = 1; i <= reader.NumberOfPages; ++i)
{
strPageNumber = i.ToString();
PdfDictionary pagedic = reader.GetPageN(i);
PdfArray annotarray = (PdfArray)PdfReader.GetPdfObject(pagedic.Get(PdfName.ANNOTS));
if (annotarray == null || annotarray.Size == 0)
{
continue;
}
// Finding out the links
foreach (object annot in annotarray.ArrayList)
{
PdfDictionary annotationDic = null;
if (annot is PdfIndirectReference)
{
annotationDic = (PdfDictionary)PdfReader.GetPdfObject((PdfIndirectReference)annot);
}
else
{
annotationDic = (PdfDictionary) annot;
}
PdfName subType = (PdfName)annotationDic.Get(PdfName.SUBTYPE);
if ((subType.Equals(PdfName.TEXT)) && (strCommentsVariables.IndexOf("text") != -1))
{
strCommentType = "text";
//break;
}
else if ((subType.Equals(PdfName.LINK)) && (strCommentsVariables.IndexOf("Link") != -1))
{
strCommentType = "Link";
//break;
}
if ((strCommentType != ""))
{
strCommentsFound = "Yes";
intCommentCount = ++intCommentCount;
strComments = strComments + "<BR>" + "A comment type of '" + "<b>" + strCommentType + "</b>" + "' has been found on page no: " + "<b>" + strPageNumber + "</b>";
if (intCommentCount == 5)
{
break;
}
else
{
strCommentType = string.Empty;
}
}
}
}
return strComments;
}
This code worked for us
var footnoteIdentifier = annotationDic.Get(PdfName.F);
if (footnoteIdentifier != null)
{
continue;
}

SGML formating Issue

I have an requirement to read the SGML file and replace if the symbol like comma(,) or full stop (.) then I need to change the symbols and save with the same SGML file itself but I am facing the format issue after replacing the content.
Below is my code and my final output would be store with the same .sgm format.
The below code is working but after replacing the values the output format is differs. Can you suggest for this scenario
Main method:
string resultValue = HTMLToEntity(ReplaceSGML(sbContent.ToString()));
StringReader sr = new StringReader(resultValue.ToString());
SgmlReader reader = new SgmlReader();
reader.WhitespaceHandling = WhitespaceHandling.All;
reader.CaseFolding = Sgml.CaseFolding.ToLower;
reader.InputStream = sr;
StringWriter sw = new StringWriter();
XmlTextWriter w = new XmlTextWriter(sw);
w.Formatting = System.Xml.Formatting.Indented;
w.WriteStartDocument();
reader.Read();
while (!reader.EOF)
{
w.WriteNode(reader, true);
}
//File.WriteAllText(#"C:\Output\test.sgm", );
w.Flush();
w.Close();
Method : ReplaceSGML
private static string ReplaceSGML(string html)
{
XmlDocument xml = new XmlDocument();
xml.Load(_xmlEnglishPath);
XmlNodeList resources = xml.SelectNodes("root/data");
_htmlEnglishDictonaries = new Dictionary<string, string>();
_htmlEnglishDictonaries.Add(";", "{After1Space}"); // replacing semicolon into {After1space}
_htmlEnglishDictonaries.Add(":", "{Before1Space}"); // replacing colon into {Before1Space}
_htmlEnglishDictonaries.Add(".", "{Before1Space}"); // replacing . into {Before1Space}
string line = string.Empty;
StringReader reader = new StringReader(html);
while (reader.Peek() > -1)
{
line = reader.ReadLine();
foreach (var events in _htmlEnglishDictonaries)
{
if (line.Contains(events.Key))
{
// Rule should be implement
// <!-- Replacetext 1.{After1Space}, 2.{Before1Space}, 3.{NoSpace}, 4. {After1LetterCaps} -->
int idx;
if (events.Value.ToLower().Trim() == "{after1space}")
{
idx = line.IndexOf(events.Key) + events.Key.Length;
if (line[idx].ToString() != " ")
{
line = line.Replace(events.Key, events.Key + " ");
}
}
if (events.Value.ToLower().Trim() == "{before1space}")
{
idx = line.IndexOf(events.Key);
if (line[idx].ToString() != " ")
{
line = line.Replace(events.Key, " " + events.Key);
}
}
if (events.Value.ToLower().Trim() == "{before1space},{after1space}")
{
idx = line.IndexOf(events.Key);
if (line[idx].ToString() != " ")
{
line = line.Replace(events.Key, " " + events.Key + " ");
}
}
if (events.Value.ToLower().Trim() == "{nospace}")
{
idx = line.IndexOf(events.Key);
if (line[idx].ToString() != " ")
{
line = line.Replace(events.Key, " " + events.Key);
}
}
if (events.Value.ToLower().Trim() == "{after1lettercaps}")
{
idx = line.IndexOf(events.Key) + events.Key.Length;
if (line[idx].ToString() != " ")
{
if (line[idx + 1].ToString() != " ")
{
line = line.Replace(events.Key, " " + events.Key + line[idx + 1].ToString().ToUpper());
}
else
{
line = line.Replace(events.Key, " " + events.Key);
}
}
}
}
}
}
return line.ToString();
}
Thanks in advance

How can I find a phrase anywhere in a String Array?

I need to see if any phrase, such as "duckbilled platypus" appears in a string array.
In the case I'm testing, the phrase does exist in the string list, as shown here:
Yet, when I look for that phrase, as shown here:
...it fails to find it. I never get past the "if (found)" gauntlet in the code below.
Here is the code that I'm using to try to traverse through the contents of one doc to see if any phrase (two words or more) are found in both documents:
private void FindAndStorePhrasesFoundInBothDocs()
{
string[] doc1StrArray;
string[] doc2StrArray;
slPhrasesFoundInBothDocs = new List<string>();
slAllDoc1Words = new List<string>();
int iCountOfWordsInDoc1 = 0;
int iSearchStartIndex = 0;
int iSearchEndIndex = 1;
string sDoc1PhraseToSearchForInDoc2;
string sFoundPhrase;
bool found;
int iLastWordIndexReached = iSearchEndIndex;
try
{
doc1StrArray = File.ReadAllLines(sDoc1Path, Encoding.UTF8);
doc2StrArray = File.ReadAllLines(sDoc2Path, Encoding.UTF8);
foreach (string line in doc1StrArray)
{
string[] subLines = line.Split();
foreach (string whirred in subLines)
{
if (String.IsNullOrEmpty(whirred)) continue;
slAllDoc1Words.Add(whirred);
}
}
iCountOfWordsInDoc1 = slAllDoc1Words.Count();
sDoc1PhraseToSearchForInDoc2 = slAllDoc1Words[iSearchStartIndex] + ' ' + slAllDoc1Words[iSearchEndIndex];
while (iLastWordIndexReached < iCountOfWordsInDoc1 - 1)
{
sFoundPhrase = string.Empty;
// Search for the phrase from doc1 in doc2;
found = doc2StrArray.Contains(sDoc1PhraseToSearchForInDoc2);
if (found)
{
sFoundPhrase = sDoc1PhraseToSearchForInDoc2;
iSearchEndIndex++;
sDoc1PhraseToSearchForInDoc2 = sDoc1PhraseToSearchForInDoc2 + ' ' + slAllDoc1Words[iSearchEndIndex];
}
else //if not found, inc vals of BOTH int args and, if sFoundPhrase not null, assign to sDoc1PhraseToSearchForInDoc2 again.
{
iSearchStartIndex = iSearchEndIndex;
iSearchEndIndex = iSearchStartIndex + 1;
if (!string.IsNullOrWhiteSpace(sFoundPhrase)) // add the previous found phrase if there was one
{
slPhrasesFoundInBothDocs.Add(sFoundPhrase);
}
sDoc1PhraseToSearchForInDoc2 = slAllDoc1Words[iSearchStartIndex] + ' ' + slAllDoc1Words[iSearchEndIndex];
} // if/else
iLastWordIndexReached = iSearchEndIndex;
} // while
} // try
catch (Exception ex)
{
MessageBox.Show("FindAndStorePhrasesFoundInBothDocs(); iSearchStartIndex = " + iSearchStartIndex.ToString() + "iSearchEndIndex = " + iSearchEndIndex.ToString() + " iLastWordIndexReached = " + iLastWordIndexReached.ToString() + " " + ex.Message);
}
}
doc2StrArray does contain the phrase sought, so why does doc2StrArray.Contains(sDoc1PhraseToSearchForInDoc2) fail?
This should do what you want:
found = Array.FindAll(doc2StrArray, s => s.Contains(sDoc1PhraseToSearchForInDoc2));
In List<T>, Contains() looking for an T, Here in your code to found be true must have all the text in particular index (NOT part of it).
Try this
var _list = doc2StrArray.ToList();
var found = _list.FirstOrDefault( w => w.Contains( sDoc1PhraseToSearchForInDoc2 ) ) != null;

How can I write data to text file one once is the same data ? And how to read back and assign?

if (GUILayout.Button("Copy settings"))
{
var selection = Selection.gameObjects.ToList();
for (int i = selection.Count - 1; i >= 0; --i)
{
var selected = selection[i];
WriteDataToFile("Transform " + i.ToString() + " Name ==> " + selected.name);
WriteDataToFile("************************" +
"********************");
if (selected.transform.parent != null)
WriteDataToFile(selected.transform.parent.ToString());
WriteDataToFile("local position " + selected.transform.localPosition.ToString());
WriteDataToFile("local rotation " + selected.transform.localRotation.ToString());
WriteDataToFile("local scale " + selected.transform.localScale.ToString());
WriteDataToFile("************************" +
"********************");
WriteDataToFile(" ");
}
}
And the WriteDataToFile:
private void WriteDataToFile(string line)
{
string path = "Assets/Resources/test.txt";
StreamWriter writer = new StreamWriter(path, true);
writer.WriteLine(line);
writer.Close();
}
First I want to check that if I click more then once on the button and it's the same data: name position rotation scale don't write again.
Second how can I read back the lines of the data and assign them into a object ? Also the name. So a new object will be created in the same name parent if the original was parent position rotation and scale.
This is how I'm reading now:
void ReadString()
{
string path = "Assets/Resources/test.txt";
StreamReader reader = new StreamReader(path);
Debug.Log(reader.ReadToEnd());
reader.Close();
}
To add new data to the file that doesn't already exist:
private void WriteDataToFile(string line)
{
string path = "Assets/Resources/test.txt";
string[] text = new string[];
if(File.Exists(path))
{
text = File.ReadAllLines(path);
if(!text.Contains(line))
File.AppendAllText(path, Line);
}
}
If you are not limited to using a text file for storing and retrieving data then I recommend finding a way to write all this data:
WriteDataToFile("Transform " + i.ToString() + " Name ==> " + selected.name);
WriteDataToFile("************************" +
"********************");
if (selected.transform.parent != null)
WriteDataToFile(selected.transform.parent.ToString());
WriteDataToFile("local position " + selected.transform.localPosition.ToString());
WriteDataToFile("local rotation " + selected.transform.localRotation.ToString());
WriteDataToFile("local scale " + selected.transform.localScale.ToString());
WriteDataToFile("************************" +
"********************");
WriteDataToFile(" ");
in less writes because opening and closing the file could be expensive. Maybe something like this:
var selected = selection[i].transform;
string toWrite = $"{parent}:{localPosition}:{localRotation}:{localScale}";
WriteDataToFile(toWrite);
This would mean retrieval would be simply - (not sure the type)
private gameObject GetObjectFromFile(Path, Id)
{
string[] text = new string[];
if(File.Exists(path))
{
text = File.ReadAllLines(path);
foreach(string s in text)
{
if(s.Split(':')[0] == Id.ToString())
{
text = s.Split(':');
break;
}
}
var Id = Convert.ToInt32(text[0]);
var localPosition = Convert.ToInt32(text[1]);
var localRotation = Convert.ToInt32(text[2]);
var localScale = Convert.ToInt32(text[3]);
return new gameObject(Id, localPosition, localRotation, localScale);
}

replacing text in a text file with \r\n

Currently I am building an agenda with extra options.
for testing purposes I store the data in a simple .txt file
(after that it will be connected to the agenda of a virtual assistant.)
To change or delete text from this .txt file I have a problem.
Although the part of the content that needs to be replaced and the search string are exactly the same it doesn't replace the text in content.
code:
Change method
public override void Change(List<object> oldData, List<object> newData)
{
int index = -1;
for (int i = 0; i < agenda.Count; i++)
{
if(agenda[i].GetType() == "Task")
{
Task t = (Task)agenda[i];
if(t.remarks == oldData[0].ToString() && t.datetime == (DateTime)oldData[1] && t.reminders == oldData[2])
{
index = i;
break;
}
}
}
string search = "Task\r\nTo do: " + oldData[0].ToString() + "\r\nDateTime: " + (DateTime)oldData[1] + "\r\n";
reminders = (Dictionary<DateTime, bool>) oldData[2];
if(reminders.Count != 0)
{
search += "Reminders\r\n";
foreach (KeyValuePair<DateTime, bool> rem in reminders)
{
if (rem.Value)
search += "speak " + rem.Key + "\r\n";
else
search += rem.Key + "\r\n";
}
}
// get new data
string newRemarks = (string)newData[0];
DateTime newDateTime = (DateTime)newData[1];
Dictionary<DateTime, bool> newReminders = (Dictionary<DateTime, bool>)newData[2];
string replace = "Task\r\nTo do: " + newRemarks + "\r\nDateTime: " + newDateTime + "\r\n";
if(newReminders.Count != 0)
{
replace += "Reminders\r\n";
foreach (KeyValuePair<DateTime, bool> rem in newReminders)
{
if (rem.Value)
replace += "speak " + rem.Key + "\r\n";
else
replace += rem.Key + "\r\n";
}
}
Replace(search, replace);
if (index != -1)
{
remarks = newRemarks;
datetime = newDateTime;
reminders = newReminders;
agenda[index] = this;
}
}
replace method
private void Replace(string search, string replace)
{
StreamReader reader = new StreamReader(path);
string content = reader.ReadToEnd();
reader.Close();
content = Regex.Replace(content, search, replace);
content.Trim();
StreamWriter writer = new StreamWriter(path);
writer.Write(content);
writer.Close();
}
When running in debug I get the correct info:
content "-- agenda --\r\n\r\nTask\r\nTo do: test\r\nDateTime: 16-4-2012 15:00:00\r\nReminders:\r\nspeak 16-4-2012 13:00:00\r\n16-4-2012 13:30:00\r\n\r\nTask\r\nTo do: testing\r\nDateTime: 16-4-2012 9:00:00\r\nReminders:\r\nspeak 16-4-2012 8:00:00\r\n\r\nTask\r\nTo do: aaargh\r\nDateTime: 18-4-2012 12:00:00\r\nReminders:\r\n18-4-2012 11:00:00\r\n" string
search "Task\r\nTo do: aaargh\r\nDateTime: 18-4-2012 12:00:00\r\nReminders\r\n18-4-2012 11:00:00\r\n" string
replace "Task\r\nTo do: aaargh\r\nDateTime: 18-4-2012 13:00:00\r\nReminders\r\n18-4-2012 11:00:00\r\n" string
But it doesn't change the text. How do I make sure that the Regex.Replace finds the right piece of content?
PS. I did check several topics on this, but none of the solutions mentioned there work for me.
You missed a : right after Reminders. Just check it again :)
You could try using a StringBuilder to build up you want to write out to the file.
Just knocked up a quick example in a console app but this appears to work for me and I think it might be what you are looking for.
StringBuilder sb = new StringBuilder();
sb.Append("Tasks\r\n");
sb.Append("\r\n");
sb.Append("\tTask 1 details");
Console.WriteLine(sb.ToString());
StreamWriter writer = new StreamWriter("Tasks.txt");
writer.Write(sb.ToString());
writer.Close();

Categories