Using a xml file as an option file - c#

I'm working on a Windows Service that uses a xml as on option file to monitor a directory, when a new file is detected in it, this file is renamed, sent by mail to a specified list of users and moved to an archive directory.
The option file used to be like this and the user would replace to what he needs everytime :
<Options>
<ExportDir>path/to/export/directory</ExportDir>
<ArchiveDir>path/to/archive/directory</ArchiveDir>
<MailTo>
<Mail>fake1#mail.com</Mail>
<Mail>fake2#mail.com</Mail>
<Mail>fake3#mail.com</Mail>
</MailTo>
</Options>
Parsing with :
List<string> mail = new List<string>();
XmlDocument doc = new XmlDocument();
doc.Load(#"path/to/xml/file");
XmlNode NodeExportDir = doc.DocumentElement.SelectSingleNode("ExportDir");
string ExportDir= NodeExportDir.InnerText;
XmlNode NodeArchiveDir = doc.DocumentElement.SelectSingleNode("ArchiveDir");
string ArchiveDir = NodeArchiveDir.InnerText;
XmlNodeList listAddress = doc.SelectNodes("//MailTo");
foreach (XmlNode node in listAddress)
{
foreach (XmlNode mailAddress in node.ChildNodes)
{
mail.Add(mailAddress.InnerText);
}
}
I want to improve it so it is possible to have several options without deleting everytime. So the xml file now looks like :
<Options>
<ExportDir path = "path/to/export/directory">
<ArchiveDir>path/to/archive/directory</ArchiveDir>
<MailTo>
<Mail>fake1#mail.com</Mail>
<Mail>fake2#mail.com</Mail>
<Mail>fake3#mail.com</Mail>
</MailTo>
</ExportDir>
<ExportDir path = "path/to/export/directory2">
<ArchiveDir>path/to/archive/directory2</ArchiveDir>
<MailTo>
<Mail>fake4#mail.com</Mail>
<Mail>fake5#mail.com</Mail>
<Mail>fake6#mail.com</Mail>
</MailTo>
</ExportDir>
</Options>
When there is only one ExportDir, I still manage to parse and put results in needed variables with this code :
List<string> mail = new List<string>();
XmlDocument doc = new XmlDocument();
doc.Load(#"path/to/xml/file");
XmlNodeList NodeExportDir = doc.GetElementsByTagName("ExportDir");
for (int i = 0; i < NodeExportDir.Count; i++)
{
string ExportDir = NodeExportDir[i].Attributes["path"].Value;
}
XmlNodeList NodeArchiveDir = doc.SelectNodes("//ArchiveDir");
foreach (XmlNode node in NodeArchiveDir)
{
foreach (XmlNode dirArch in node.ChildNodes)
{
string ArchiveDir = dirArch.InnerText;
}
}
XmlNodeList listAddress = doc.SelectNodes("//MailTo");
foreach (XmlNode node in listAddress)
{
foreach (XmlNode mailAddress in node.ChildNodes)
{
mail.Add(mailAddress.InnerText);
}
}
But when there are more than one, of course it doesn't work as I'd like.
How can I use the archive directory and the mail addresses according to where the user drops a file (which is the ExportDir).
Update :
Thanks to ivcubr's answer I can now select the informations corresponding to an ExportDir path like so :
foreach (DossierExport test in options)
{
if (test.Path == "path/to/export/directory")
{
string ArchiveDir = test.Archive;
foreach (string mailAddr in test.Mail)
{
mail.Add(mailAddr);
}
}
}
How could I make a general solution so I don't have to add code everytime a new ExportDir path is created in the xml file ?

If you are most comfortable with XmlDocument the following should work for you. Here I would recommend creating a class to hold each option and then later you can just loop through all of these and use the properties parsed from the XML file.
XmlDocument doc = new XmlDocument();
doc.Load(#"test.xml");
List<ExportDirectory> options = new List<ExportDirectory>();
XmlNodeList nodeExportDir = doc.GetElementsByTagName("ExportDir");
foreach (XmlNode node in nodeExportDir) {
ExportDirectory exportDirectory = new ExportDirectory() {
Path = node.Attributes["path"].Value,
Archive = node.SelectSingleNode("ArchiveDir").InnerText
};
foreach (XmlNode mail in node.SelectNodes("MailTo/Mail")) {
exportDirectory.Mail.Add(mail.InnerText);
}
options.Add(exportDirectory);
}
Export Directory class:
public class ExportDirectory {
public string Path { get; set; }
public string Archive { get; set; }
public List<string> Mail { get; set; }
public ExportDirectory() {
Mail = new List<string>();
}
}
EDIT:
Regarding your added question, why do you need to have if (test.Path == "path/to/export/directory")? Why not eliminate the if statement and go with:
foreach (DossierExport test in options)
{
string ArchiveDir = test.Archive;
foreach (string mailAddr in test.Mail)
{
mail.Add(mailAddr);
}
}

Related

Parsing XML with C# - Not able to select Nodes while GetElementsByTagName working

I'm trying to select nodes in c# within a XML file with below example.
<dsEq xmlns="http://tempuri.org/dsEq.xsd">
<Dago>
<EID>XX</EID>
below code is working :
private static List<string> getListOfEID(XmlDocument xmlDoc)
{
List<string> ListingEID = new List<string>();
XmlNodeList nodeCollection = xmlDoc.GetElementsByTagName("EID");
foreach (XmlNode elt in nodeCollection)
{
ListingEID.Add(elt.InnerText.ToString());
}
return ListingEID;
}
While I tried a lot of things, without success with Selectnodes method, not working :
private static List<string> getListOfEID(XmlDocument xmlDoc)
{
List<string> ListingEID = new List<string>();
XmlNodeList nodeCollection = xmlDoc.SelectNodes("/dsEq/Dago/EID");
foreach (XmlNode elt in nodeCollection)
{
ListingEID.Add(elt.InnerText.ToString());
}
return ListingEID;
}
Thanks in advance !
tried a lot of different xPath without success.
adding nameSpace seems not helping
private static List<string> getListOfEID(XmlDocument xmlDoc)
{
List<string> ListingEID = new List<string>();
XmlNamespaceManager nameManager = new XmlNamespaceManager(xmlDoc.NameTable);
nameManager.AddNamespace("myEID","/dsEq/Dago");
XmlNodeList nodeCollection = xmlDoc.SelectNodes("myEID");
foreach (XmlNode elt in nodeCollection)
{
ListingEID.Add(elt.InnerText.ToString());
}
return ListingEID;
}
==> Show nil node
It is very easy via LINQ to XML.
LINQ to XML is available in the .Net Framework since 2007.
c#
void Main()
{
XDocument xdoc = XDocument.Parse(#"<dsEq xmlns='http://tempuri.org/dsEq.xsd'>
<Dago>
<EID>XX</EID>
</Dago>
<Dago>
<EID>YY</EID>
</Dago>
</dsEq>");
XNamespace ns = xdoc.Root.GetDefaultNamespace();
List<String> listOfStrings = xdoc.Descendants(ns + "EID")
.Select(x => x.Value).ToList();
Console.Write(listOfStrings);
}
I too think it is super simple with Linq to XML as #Yitzhak Khabinsky showed already. Yours is not working because you are not using Xpath correctly.
void Main()
{
var doc = new XmlDocument();
doc.LoadXml(sample);
var eids = getListOfEID(doc);
foreach (var eid in eids)
{
Console.WriteLine(eid);
}
}
private static List<string> getListOfEID(XmlDocument xmlDoc)
{
List<string> ListingEID = new List<string>();
XmlNamespaceManager nameManager = new XmlNamespaceManager(xmlDoc.NameTable);
nameManager.AddNamespace("x","http://tempuri.org/dsEq.xsd");
XmlNodeList nodeCollection = xmlDoc.SelectNodes("//x:EID", nameManager);
foreach (XmlNode elt in nodeCollection)
{
ListingEID.Add(elt.InnerText.ToString());
}
return ListingEID;
}
static readonly string sample = #"<dsEq xmlns=""http://tempuri.org/dsEq.xsd"">
<Dago>
<EID>XX</EID>
</Dago>
<Dago>
<EID>YY</EID>
</Dago>
<Dago>
<EID>ZZ</EID>
</Dago>
</dsEq>";

How to read values from a table in a word doc using C#

I'm trying to connect to a Microsoft word document (.docx) to read values from tables located in the .docx. I'm using Open-XML SDK 2.0 to make the connection to the .docx file. So far after looking for examples and ideas, I have this,
public static string TextFromWord(string file)
{
const string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
StringBuilder textBuilder = new StringBuilder();
using (WordprocessingDocument wDoc = WordprocessingDocument.Open(filename,false))
{
//Manage namespaces to perform Xpath queries
NameTable nt = new NameTable();
XmlNamespaceManager nsManger = new XmlNamespaceManger(nt);
nsManager.AddNamespace("w", wordmlNamespace);
//Get the document part from the package.
//Load the XML in the document part into an XmlDocument instance.
XmlDocument xdoc = new XmlDocument(nt);
xdoc.Load(wdDoc.MainDocumentPart.GetStream());
XmlNodeList paragraphNodes = xdoc.SelectNodes("//w:p", nsManager);
foreach (XmlNode paragraphNode in paragraphNodes)
{
XmlNodeList textNodes = paragraphNode.SelectNodes(".//w:t", nsmanager);
foreach (System.Xml.XmlNode textNode in textNodes)
{
textBuilder.Append(textNode.InnerText);
}
textBuilder.Append(Environment.NewLine);
}
}
return textBuilder.ToString();
}
The code works when there is just text in the .docx but fails when the text is in tables. Is there a way to fix this so it can work with tables in a .docx?
Try the following simple re-write of your method. It replaces your System.XML calls and namespace items with OpenXML elements (Document, Body, Paragraph, Table, Row, Cell, Descendants, etc) . Please install and use the OpenXML 2.5 SDK.
public static string TextFromWord(string filename)
{
StringBuilder textBuilder = new StringBuilder();
using (WordprocessingDocument wDoc = WordprocessingDocument.Open(filename, false))
{
var parts = wDoc.MainDocumentPart.Document.Descendants().FirstOrDefault();
if (parts != null)
{
foreach (var node in parts.ChildElements)
{
if(node is Paragraph)
{
ProcessParagraph((Paragraph)node, textBuilder);
textBuilder.AppendLine("");
}
if (node is Table)
{
ProcessTable((Table)node, textBuilder);
}
}
}
}
return textBuilder.ToString();
}
private static void ProcessTable(Table node, StringBuilder textBuilder)
{
foreach (var row in node.Descendants<TableRow>())
{
textBuilder.Append("| ");
foreach (var cell in row.Descendants<TableCell>())
{
foreach (var para in cell.Descendants<Paragraph>())
{
ProcessParagraph(para, textBuilder);
}
textBuilder.Append(" | ");
}
textBuilder.AppendLine("");
}
}
private static void ProcessParagraph(Paragraph node, StringBuilder textBuilder)
{
foreach(var text in node.Descendants<Text>())
{
textBuilder.Append(text.InnerText);
}
}
Note - this code will only work on simple Word documents that consist of Paragraphs and Tables. This code has not been tested on complex word documents.
The following document was processed with the above code in a Console app:
Here is the text output:

Recursive method resulting in infinite loop

I am doing a migration tool for sharepoint sites. I migrate sites using XML files. In my method for generating my XML file, however, I get an infinite loop and I can't figure out the cause. I get no errors what so ever but the XML file gets understandably large.
How I create the root node of my XML file:
private void createRootXmlNodeFromRootSite()
{
if (rootsite != null)
{
//Add root-site to TreeView
XmlElement rootnode = document.CreateElement(strRoot);
rootnode.SetAttribute(strTitle, rootsite.Title);
rootnode.SetAttribute(strName, rootsite.SiteAdministrators[0].Name);
rootnode.SetAttribute(strEmail, rootsite.SiteAdministrators[0].Email);
document.AppendChild(rootnode);
XmlElement nodelists = document.CreateElement(strLists);
if (rootsite.Lists.Count > 0)
{
rootnode.AppendChild(nodelists);
//Get all lists within root-site and put them under it's node.
foreach (SPList list in rootsite.Lists)
{
XmlElement nodelist = document.CreateElement(strList);
nodelist.SetAttribute(strTitle, list.Title);
if (!list.Hidden)
{
nodelists.AppendChild(nodelist);
XmlElement nodects = document.CreateElement(strContentTypes);
if (list.ContentTypes.Count > 0)
{
nodelist.AppendChild(nodects);
//Get all content types within lists of root site and put them under it's node.
foreach (SPContentType ct in list.ContentTypes)
{
XmlElement nodect = document.CreateElement(strContentType);
nodect.SetAttribute(strTitle, ct.Name);
if (!ct.Hidden)
{
nodects.AppendChild(nodect);
XmlElement nodefields = document.CreateElement(strFields);
if (ct.Fields.Count > 0)
{
nodect.AppendChild(nodefields);
foreach (SPField field in ct.Fields)
{
XmlElement nodefield = document.CreateElement(strField);
nodefield.SetAttribute(strTitle, field.Title);
if (!field.Hidden)
{
nodefields.AppendChild(nodefield);
}
}
}
}
}
}
}
//Call recursive methods to get child-site-elements.
convertSiteCollectionToXml(rootnode, rootsite);
}
}
}
}
This method then calls a recursive method which creates child nodes from the subsites:
private void convertSiteCollectionToXml(XmlNode nodeElement, SPWeb site)
{
XmlElement nodesites = document.CreateElement("Sites");
if (site.Webs.Count > 0)
{
nodeElement.AppendChild(nodesites);
foreach (SPWeb childsite in site.Webs)
{
XmlElement child = document.CreateElement("Site");
child.SetAttribute("Title", childsite.Title);
nodesites.AppendChild(child);
XmlElement nodelists = document.CreateElement("Lists");
if (childsite.Lists.Count > 0)
{
child.AppendChild(nodelists);
foreach (SPList list in childsite.Lists)
{
XmlElement nodelist = document.CreateElement("List");
nodelist.SetAttribute("Title", list.Title);
if (!list.Hidden)
{
nodelists.AppendChild(nodelist);
XmlElement nodects = document.CreateElement("ContentTypes");
if (list.ContentTypes.Count > 0)
{
nodelist.AppendChild(nodects);
foreach (SPContentType ct in list.ContentTypes)
{
XmlElement nodect = document.CreateElement("ContentType");
nodect.SetAttribute("Title", ct.Name);
if (!ct.Hidden)
{
nodects.AppendChild(nodect);
XmlElement nodefields = document.CreateElement("Fields");
if (ct.Fields.Count > 0)
{
nodect.AppendChild(nodefields);
foreach (SPField field in ct.Fields)
{
XmlElement nodefield = document.CreateElement("Field");
nodefield.SetAttribute("Title", field.Title);
if (!field.Hidden)
{
nodefields.AppendChild(nodefield);
}
}
}
}
}
}
}
convertSiteCollectionToXml(child, childsite);
}
}
}
}
}
The infinite loop happens in the second method. Anyone see the cause?

No Collapse/Expand icon in TreeView when using ASP.NET and C#

I have a trouble with Expand / Collapse icon in TreeView
What I get : http://i.imgur.com/dl5Lg.jpg
What I did :
C# code :
public static void TreeLoad(TreeView tree, string #source)
{
XmlDocument document = new XmlDocument();
//TreeView tree = new TreeView();
try
{
if (File.Exists(source))
{
document.Load(source);
tree.Nodes.Clear();
XmlNodeList category = document.SelectNodes("/parent/Categories");
//XmlNodeList links = document.SelectNodes("/parent/Categories/link");
foreach (XmlNode node in category)
{
TreeNode t1 = new TreeNode(node.Attributes["Name"].Value);
tree.Nodes.Add(t1);
//t1.ShowCheckBox = true;
if (node.HasChildNodes)
{
//foreach (XmlNode nod in links)
foreach (XmlNode nod in node.ChildNodes)
{
TreeNode t2 = new TreeNode(nod.Attributes["name"].Value);
tree.Nodes.Add(t2);
}
}
}
//tree.Nodes[0].CollapseAll();
//document.Save(source);
}
else
{
messages = NOTFOUND;
}
}
catch (Exception ect)
{
//exist.InnerText = ect.Message;
messages = ect.Message;
}
finally
{
// document.Save(source);
}
//return tree;
}
URLStorageCtrl.TreeLoad(tree, "example.xml");
ASP.NET code
<asp:TreeView ID="tree" runat="server"></asp:TreeView>
I'm using 4-tier architecture so please do not redirect me to design page, I use only coding.
yeah, of course. you added all nodes to tree as its root.
this code:
tree.Nodes.Add(t2);
change to :
t1.ChildNodes.Add(t2);

Add XmlNode to XmlElement

I get a soap envelope back from a web service with customer data such as name and address etc. The address does not contain city/suburb but postcode. I have all the city and suburbs with their post codes in a CSV file so I want to insert the correct name for each post code. I can store it in a database or something else but this is more about how to insert the node before I pass the data on.
The code is like :
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(searchResponse);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xDoc.NameTable);
nsmgr.AddNamespace("ns", wsNamespace);
XmlNodeList postCodeNodes = xDoc.SelectNodes("//ns:postcode", nsmgr);
string applicationPath = AppDomain.CurrentDomain.BaseDirectory;
foreach (XmlNode node in postCodeNodes)
{
using (StreamReader readFile = new StreamReader(applicationPath + "postcodes.csv"))
{
string line;
string[] row;
while ((line = readFile.ReadLine()) != null)
{
row = line.Split(',');
if (row[0].ToString() == node.InnerText)
{
string suburb = row[1].ToString();
//XmlNode ndSuburb = xDoc.CreateElement("suburb");
//ndSuburb.Value = suburb;
//node.ParentNode.AppendChild(ndSuburb);
break;
}
}
}
}
and I am not sure what to do where I have commented out the code. Any suggestions? Tips on how to make this more efficient would also be appreciated.
Thanks in advance.
Well, it's a bit hard to know without actually seeing the XML structure that exists and the desired new XML structure. Basically I'd assume that you want a new XML node containing the suburb at the same level as the postcode element.
In that case, I'd used:
XmlElement elem = xDoc.CreateElement("suburb");
elem.InnerText = ...;
node.ParentNode.AppendChild(elem);
EDIT
As for efficiency: Why don't you read your "postcode file" only once, adding the entries to a dictionary that contains the post code as a key and the suburb as a value? That's far quicker than reading the file every time.
Dictionary<string, string> postCodeMap = new Dictionary<string, string>();
string[] lines = File.ReadAllLines(...);
foreach (string line in lines)
{
string[] parts = line.Split(',');
postCodeMap[parts[0]] = parts[1];
}
And later do:
foreach (XmlNode node in postCodeNodes)
{
string suburb = postCodeMap[node.InnerText];
XmlElement elem = xDoc.CreateElement("suburb");
elem.InnerText = suburb;
node.ParentNode.AppendChild(elem);
}

Categories

Resources