I have an xml document of type like this:
<?xml version="1.0" encoding="UTF-16"?>
<Recordset>
<Table>Recordset</Table>
<Rows>
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Scheduled</Value>
</Field>
<Field>
<Alias>U_Revision</Alias>
<Value>code00</Value>
</Field>
<Field>
<Alias>U_Quantity</Alias>
<Value>10.000000</Value>
</Field>
<Field>
<Alias>U_ActualQty</Alias>
<Value>0.000000</Value>
</Field>
...
</Fields>
</Row>
...
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Scheduled</Value>
</Field>
<Field>
<Alias>U_Revision</Alias>
<Value>code00</Value>
</Field>
<Field>
<Alias>U_Quantity</Alias>
<Value>150.000000</Value>
</Field>
<Field>
<Alias>U_ActualQty</Alias>
<Value>0.000000</Value>
</Field>
...
</Fields>
</Row>
</Rows>
</Recordset>
I have different values in field with alias of StatusName. There are some Scheduled, notScheduled, Realeased, Finished etc values. What I would like to do is to delete each node that contain node with alias StatusName and value lets say Scheduled or Finished.
I was thinking to do this more or less in that way however I am doing something wrong. May anybody let me on right way ?
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
XmlNodeList nodes = xmlDocument.SelectNodes("//Rows[#StatusName='Finished']");
for (int i = nodes.Count - 1; i >= 0; i--)
{
nodes[i].ParentNode.RemoveChild(nodes[i]);
}
var newXml = nodes.ToString();
I would like to delete the whole node if contains with alias StatusName and specific value lets say Finished.
I would expect the result in new string variable.
I like to work with DataTable with xml, I found it very easy.
I used a DataTable to work with your nodes.
So, I took your xml file and wrote some code for you that might help you:
//READ THE XML FILE
XmlDocument xmlDoc = new XmlDocument();
//My path
xmlDoc.LoadXml(Properties.Resources.test);
//Read the xml file into a dataSet
DataSet ds = new DataSet();
XmlNodeReader xnr = new XmlNodeReader(xmlDoc);
ds.ReadXml(xnr);
//Your data will be store in the 4's dataTable of the dataSet ( the <field> )
for(int i=0;i<ds.Tables[4].Rows.Count;i++)
{
//Check the value as you wish
//Here i want to suppress all the <Field> nodes with <Value> = "Scheduled"
if ( ds.Tables[4].Rows[i]["Value"].ToString().Equals("Scheduled"))
{
//RemoteAt will remove all the node, so the node <Field> in your example data
ds.Tables[4].Rows.RemoveAt(i);
//If you want to only remove the node <Value> (and not all the <Field> node ) just do ds.Tables[4].Rows["Value"]=null;
}
}
//Write your new content in a new xml file
//As you wanted here you just read the new xml file created as a string
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
ds.WriteXml(xmlTextWriter);
xmlTextWriter.Flush();
stringWriter.GetStringBuilder().ToString();
//Here the result is in stringWriter, and there is 6 <Field> nodes, and not 8 like before the suppress
}
//If you want to create a new xml file with the new content just do
ds.WriteXml(yourPathOfXmlFile);
//( like rewriting the previous xml file )
I assume, you are going to delete entire <Row> which matches your condition
i.e.,
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Finished</Value>
</Field>
</Fields>
</Row>
The required XPath:
//Row[Fields[Field[Alias[text()='StatusName'] and Value[text() = 'Finished']]]]
C#
string xPath = #"//Row[Fields[Field[Alias[text()='StatusName'] and Value[text() = 'Finished']]]]";
var nodes = xmlDocument.SelectNodes(xPath);
for (int i = nodes.Count - 1; i >= 0; i--)
{
nodes[i].ParentNode.RemoveChild(nodes[i]);
}
var newXml = xmlDocument.OuterXml;
Related
Let's suppose i have the following Xml:
<Sections>
<Section>
<Item>
<Field> myfield </Field>
<Field> myfield </Field>
</Item>
<Item>
<Field> myfield </Field>
<Field> myfield </Field>
</Item>
</Section>
<Section>
<Item>
<Field> myfield </Field>
<Field> myfield </Field>
</Item>
</Section>
</Sections>
Now what i want is to loop though Sections, and work on each item separately, so i was thinking to do something like the following:
reader.ReadToDescendant("Section")
do
{
Console.WriteLine("Section");
reader.ReadToDescendant("Item");
do
{
var element = (XElement)XNode.ReadFrom(reader);
foreach (XElement el in element.Elements())
{
Console.WriteLine(el.Value);
}
}while(reader.ReadToNextSibling("Item"));
}while (reader.ReadToNextSibling("Section"))
My question is. If i repeat the same do-while loop for Item nodes, does the reader stop when it finds the closing Section tag or it will search in all the xml? Should i use reader.ReadSubtree() before the inner loop?
Note that i'm not looking for standard answer like "Use XDocument". I know that dom are easier to use, but they are not suitable for my situation
Use ReadSubtree to create inner reader to work with current node. Without this, the reader will not stop and continue the search until the end of the document.
reader.ReadToDescendant("Section");
do
{
Console.WriteLine("Section");
using (var innerReader = reader.ReadSubtree())
{
while (innerReader.ReadToFollowing("Field"))
{
Console.WriteLine("field");
}
}
} while (reader.ReadToNextSibling("Section"));
I have the following XML doc... I have just left one "line" in for simplicity.
<?xml version="1.0" encoding="UTF-8"?>
<files>
<file type="INVOICES">
<document>blah.pdf</document>
<line>
<field name="JobNo">321654</field>
<field name="Issues">1</field>
<field name="PageCount">200</field>
<field name="PrintRun">250</field>
<field name="Size">Small</field>
</line>
</file>
</files>
C# code:
static void Main(string[] args)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("input.xml");
XmlNodeList itemNodes = xmlDoc.SelectNodes("//files/file/line");
foreach (XmlNode itemNode in itemNodes)
{
XmlNode jobNo = itemNode.SelectSingleNode("field");
if (jobNo != null)
Console.WriteLine(jobNo.InnerText);
}
Console.ReadKey();
}
This iterates through each line and displays the job number however I want to access the field by it's name JobNo i.e.
<field name="JobNo">321654</field> accessed with...
jobNo = itemNode.SelectSingleNode("JobNo");
I know I can change the xml but the XML is supplied by a customer so this is not really an option.
You can access that specific field element by providing the attribute name and value such as
XmlNode jobNo = itemNode.SelectSingleNode("field[#name='JobNo']");
I have the following XML snippet-
-<Row>
<RowType Id="1"Label="Scotland">1985</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">18</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
-<Row>
<RowType Id="1"Label="England">1986</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">19</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
-<Row>
<RowType Id="1"Label="Wales">1987</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">20</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
I am using XmlReader to retrieve specific data from it like so -
using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
{
string country = "";
string Year = "";
string count = "";
string tss= "";
string tss2 = "";
reader.MoveToContent();
while (reader.Read())
{
reader.ReadToFollowing("RowType");
country = reader.GetAttribute("Label");
country = country.Replace("'", "");
reader.ReadToFollowing("Year");
Year = reader.GetAttribute("Label");
reader.ReadToFollowing("Value");
count = reader.ReadElementContentAsString();
reader.ReadToFollowing("Field");
tss = reader.GetAttribute("Label");
reader.ReadToFollowing("Field");
tss2 = reader.GetAttribute("Label");
}
}
This is working fine for the first iteration, however on the second, it retrieves the values from the third row in the XML, and continues to skip to the next row after the one it should be parsing.
How can I resolve this?
Actually, your code is right; what is not right is the structure of the document. Or better, your code does not account for the specific structure of the document.
You can change that by adding the following bit:
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader reader = XmlReader.Create(new StringReader(xml), settings))
By default the XMLReader expects ConformanceLevel.Document and thus the file should have a structure like the following one:
<main>
<Row id="5">
<RowType Id="1" Label="Scotland">1985</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">18</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
<Row id="1">
<RowType Id="1" Label="England">1986</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">19</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
<Row id="4">
<RowType Id="1" Label="Wales">1987</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">20</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
</main>
I understand that the lack of separation between elements (e.g., Id="1"Label="Scotland" instead of Id="1" Label="Scotland") is a typo because separations have to exist in any case.
------------------- UPDATE
You report that your code does not deliver the expected result even after changing the conformance level. I have done a new test of your code and it works fine; at least, it iterates correctly. Thus, what I understand is that you want to retrieve different values than what you code does (it mixes app names, attributes and content).
Below you can see my own code (although I insist that yours iterates through the given information OK, too), which is more adaptable than yours; I am also including some comments in the parts where I think that you want to retrieve different information than what your code does. The basic idea is just retrieving information from the content (content), but your code takes it from anywhere.
string path = #"XML file";
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader reader = XmlReader.Create(path, settings))
{
string country = "";
string Year = "";
string count = "";
string tss = "";
string tss2 = "";
while (reader.ReadToFollowing("Row"))
{
XmlReader reader2 = reader.ReadSubtree();
while (reader2.Read())
{
if (reader2.NodeType == XmlNodeType.Element)
{
if (reader2.Name == "RowType")
{
country = reader2.GetAttribute("Label");
country = country.Replace("'", ""); //country_year = reader.ReadElementContentAsString(); -> "Scotland" -> 1985
}
else if (reader2.Name == "Year")
{
//IF XML IS -> <Year Id="11">1994<Year/>
//Then -> Year = reader2.GetAttribute("Label")
Year = reader2.GetAttribute("Label"); //-> 1994
}
else if (reader2.Name == "Value")
{
count = reader2.ReadElementContentAsString();
}
else if (reader2.Name == "Field")
{
if (reader2.GetAttribute("Label") == "Country")
{
tss = reader2.ReadElementContentAsString(); //I understand that this is what you want to read, instead the Label name
}
else if (reader2.GetAttribute("Label") == "Soccer")
{
tss2 = reader2.ReadElementContentAsString();//I understand that this is what you want to read, instead the Label name
}
}
}
}
}
}
This should deliver what you are looking for; or, in the worst scenario, a much clear idea about how to deal with the XML reading. Also it might be a good thing to include a try...catch just in case; note that any error while reading/dealing with the variables would provoke the reading process to be immediately stopped.
We can use LINQ to get this done if you want.
If you really want to read all the values from Xml into some variable....you can try something in similar lines...
XElement po = XElement.Load(#"SoccerCup.xml");
IEnumerable<XElement> childElements =
from el in po.Elements()
select el;
foreach (XElement el in childElements)
{
var Year=el.Element("Year").Value;
var country = el.Element("country").Value;
var count =el.Elemet("Value").Value;
Console.WriteLine("Year: " + Year);
Console.WriteLine("Country: " + country);
Console.WriteLine("Count: " + count);
}
Hope this helps...
I've got XML that describes certain data (a template) that I want to be able to edit. I load XML into DataSet (see fig. 1 below), plug DataSet tables into DataGridView (switch between them using a separate comboBox), make changes and then save XML (simple DataSet.WriteXML directive). The XML I read looks very nice and humanly readable (see fig. 2 below), however, the written XML is nowhere near the original (see fig. 3 below).
My goal is to allow editing of XML document and preserve it in the same form on save.
What am I doing wrong? Code/XML blocks are below.
fig.1 - Reading XML into DataSet:
using (XmlReader xrMeta = XmlReader.Create(new StreamReader(ofdOpenXML.FileName)))
{
while (!xrMeta.EOF)
{
xrMeta.ReadToFollowing("record");
if (xrMeta.NodeType == XmlNodeType.Element)
{
xrMeta.ReadToFollowing("fields");
xrSub = xrMeta.ReadSubtree();
dt = new DataTable();
ds = new DataSet();
ds.ReadXml(xrSub);
dt = ds.Tables[0].Copy();
dt.TableName = "recordTypeId " + iTableNumber.ToString().PadLeft(2, '0');
MetaXML.Tables.Add(dt);
iTableNumber++;
}
}
dgvMetaXML.DataSource = MetaXML.Tables[0];
fig.2 - Input XML:
<?xml version='1.0'?>
<records>
<record>
<recordTypeId>01</recordTypeId>
<fields>
<field>
<fieldNID>entityID</fieldNID>
<fieldID>1</fieldID>
<fieldName>Entity ID</fieldName>
<fieldStartPos>1</fieldStartPos>
<fieldEndPos>6</fieldEndPos>
<fieldLength>6</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue></fieldDefaultValue>
</field>
<field>
<fieldNID>reserved0101</fieldNID>
<fieldID>2</fieldID>
<fieldName>Reserved</fieldName>
<fieldStartPos>7</fieldStartPos>
<fieldEndPos>8</fieldEndPos>
<fieldLength>2</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue> </fieldDefaultValue>
</field>
<field>
<fieldNID>deviceID</fieldNID>
<fieldID>3</fieldID>
<fieldName>Device ID</fieldName>
<fieldStartPos>9</fieldStartPos>
<fieldEndPos>23</fieldEndPos>
<fieldLength>15</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue></fieldDefaultValue>
</field>
</fields>
</record>
<record>
<recordTypeId>02</recordTypeId>
<fields>
<field>
<fieldNID>userID</fieldNID>
<fieldID>1</fieldID>
<fieldName>User ID</fieldName>
<fieldStartPos>1</fieldStartPos>
<fieldEndPos>6</fieldEndPos>
<fieldLength>6</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue></fieldDefaultValue>
</field>
<field>
<fieldNID>reserved0201</fieldNID>
<fieldID>2</fieldID>
<fieldName>Reserved</fieldName>
<fieldStartPos>7</fieldStartPos>
<fieldEndPos>8</fieldEndPos>
<fieldLength>2</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue> </fieldDefaultValue>
</field>
<field>
<fieldNID>testField</fieldNID>
<fieldID>3</fieldID>
<fieldName>Test Sequence</fieldName>
<fieldStartPos>9</fieldStartPos>
<fieldEndPos>23</fieldEndPos>
<fieldLength>15</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue></fieldDefaultValue>
</field>
</fields>
</record>
</records>
fig.3 - output XML:
<records>
<recordTypeId_x0020_01>
<fieldNID>entityID</fieldNID>
<fieldID>1</fieldID>
<fieldName>Entity ID</fieldName>
<fieldStartPos>1</fieldStartPos>
<fieldEndPos>6</fieldEndPos>
<fieldLength>6</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue />
</recordTypeId_x0020_01>
<recordTypeId_x0020_01>
<fieldNID>reserved0101</fieldNID>
<fieldID>2</fieldID>
<fieldName>Reserved</fieldName>
<fieldStartPos>7</fieldStartPos>
<fieldEndPos>8</fieldEndPos>
<fieldLength>2</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue />
</recordTypeId_x0020_01>
<recordTypeId_x0020_01>
<fieldNID>deviceID</fieldNID>
<fieldID>3</fieldID>
<fieldName>Device ID</fieldName>
<fieldStartPos>9</fieldStartPos>
<fieldEndPos>23</fieldEndPos>
<fieldLength>15</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue />
</recordTypeId_x0020_01>
<recordTypeId_x0020_02>
<fieldNID>userID</fieldNID>
<fieldID>1</fieldID>
<fieldName>User ID</fieldName>
<fieldStartPos>1</fieldStartPos>
<fieldEndPos>6</fieldEndPos>
<fieldLength>6</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue />
</recordTypeId_x0020_02>
<recordTypeId_x0020_02>
<fieldNID>reserved0201</fieldNID>
<fieldID>2</fieldID>
<fieldName>Reserved</fieldName>
<fieldStartPos>7</fieldStartPos>
<fieldEndPos>8</fieldEndPos>
<fieldLength>2</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue />
</recordTypeId_x0020_02>
<recordTypeId_x0020_02>
<fieldNID>testField</fieldNID>
<fieldID>3</fieldID>
<fieldName>Test Sequence</fieldName>
<fieldStartPos>9</fieldStartPos>
<fieldEndPos>23</fieldEndPos>
<fieldLength>15</fieldLength>
<fieldType>Alpha</fieldType>
<fieldRequired>Y</fieldRequired>
<fieldDefaultValue />
</recordTypeId_x0020_02>
</records>
Your code is reading to the next fields entry with each iteration using
xrMeta.ReadToFollowing("fields");
You're then renaming the base table from fields to recordTypeId XX with
dt.TableName = "recordTypeId " + iTableNumber.ToString().PadLeft(2, '0');`
and the space is being encoded to _x0020_ to avoid breaking up the tag.
You then add this renamed instance of fields back to the root with
MetaXML.Tables.Add(dt);
The output is a result of this.
What different result were you trying to achieve?
> The XML I read looks very nice and humanly readable (see fig. 2 below), however,
> the written XML is nowhere near the original (see fig. 3 below).
> What am I doing wrong?
the dotnet dataset can only write the xml-format of its internal representation.
this representation is similar to
<datasetName>
<dataTableName OtherFieldName='value'>
<FieldName>value</FieldName>
</dataTableName>
</datasetName>
So fields are elements or attributs. Your xml structre is more complex.
The dataset tries to interprete your data and puts the data into its internal structure, if possible. In your example the information recordTypeId is lost.
I had a similar problem and created a my own xml-post-processer that reformats the xml-output to my own xml-format that dataset can read but not write.
Ended up going with k3b's approach (sorry, can't upvote - need more reputation).
Here's the updated code to read the XML into DataSet (keep in mind, it's just a mock code to make things work for the first time. You should revise it to be more efficient and ultimately make more sense):
int iTableNumber = 1;
// Read input XML
using (XmlReader xrMeta = XmlReader.Create(new StreamReader(ofdOpenXML.FileName)))
{
while (!xrMeta.EOF)
{
// Advance to next <record>
xrMeta.ReadToFollowing("record");
if (xrMeta.NodeType == XmlNodeType.Element)
{
// Advance to the next <fields>
xrMeta.ReadToFollowing("fields");
// Read underlying XML - it will be a set of flat tables
xrSub = xrMeta.ReadSubtree();
dt = new DataTable();
ds = new DataSet("fields");
ds.ReadXml(xrSub);
dt = ds.Tables[0].Copy();
dt.TableName = "field_" + iTableNumber.ToString().PadLeft(2, '0');
MetaXML.Tables.Add(dt);
iTableNumber++;
}
}
}
// Populate comboBox to switch between tables in DataSet
for (int i = 0; i < MetaXML.Tables.Count; i++)
{
cbShowTable.Items.Add(MetaXML.Tables[i].TableName);
}
// Populate DataGridView with first read table
dataGridViewMetaXML.DataSource = MetaXML.Tables[0];
Saving XML now looks like this:
// This is our output XML file
// Technically, it should have been the same name as the input one
// but for the purposes of testing it isn't
StreamWriter srFile = new StreamWriter((#"testingOutputXML.xml"));
StringWriter stWriter;
StringBuilder sbXML = new StringBuilder();
// Headers to play nice
sbXML.AppendLine("<?xml version='1.0'?>");
sbXML.AppendLine("<records>");
DataTable dt;
for (int i = 0; i < MetaXML.Tables.Count; i++)
{
// This is where we have to recreate the structure manually
sbXML.AppendLine("<record>");
sbXML.Append("<recordTypeId>");
sbXML.Append((1+ i).ToString().PadLeft(2,'0'));
sbXML.AppendLine("</recordTypeId>");
dt = new DataTable();
dt = MetaXML.Tables[i].Copy();
dt.TableName = "field";
stWriter = new StringWriter();
dt.WriteXml(stWriter, false);
stWriter.WriteLine();
sbXML.Append(stWriter.GetStringBuilder());
// Need to clean up because DataTable's WriteXML() method
// wraps the data in <DocumentElement> and </DocumentElement> tags
sbXML.Replace("DocumentElement", "fields");
sbXML.AppendLine("</record>");
}
sbXML.AppendLine("</records>");
srFile.Write(sbXML.ToString());
srFile.Flush();
srFile.Close();
MessageBox.Show("Done!");
Thanks everyone who chipped in with the answers, it steered me to the right track.
I have been dealing with this xml document for a long time, and now it turns out that user should be able to see certain fields based on the permission level defined in the xml document. This is what the document looked previously:
<?xml version="1.0" encoding="utf-8" ?>
<AccessControl>
<Field>
<name>First_Name</name>
<label>First Name</label>
</Field>
<Field>
<name>Last_Name</name>
<label>Last Name</label>
</Field>
......
.....
</AccessControl>
This is how is parsed the document:
doc.Load(System.Web.Hosting.HostingEnvironment.MapPath("~/ConfigFile.xml"));
XmlNode root = doc.DocumentElement;
XmlNodeList xnList = root.SelectNodes("/AccessControl/Field");
foreach (XmlNode xn in xnList)
{
string fieldName = xn["name"].InnerText;
.....
....
}
Now I am adding a few permission nodes in the document, which will contain field nodes, similar to this:
<AccessControl>
<Permission Name = "permissionXYZ" >
<Field>
<name>First_Name</name>
<label>First Name</label>
</Field>
<Field>
<name>Last_Name</name>
<label>Last Name</label>
</Field>
....
</Permission>
<Permission Name = "permission123" >
<Field>
...
...
</Field>
</Permission>
</AccessControl>
How do I get only the required fields based on the permission defined in the document??
Using LINQ you could filter based on the attribute value and then select the Fields. Test.xml file in my example just holds your sample XML.
XDocument document = XDocument.Load("c:\\temp\\test.xml");
var fields = document.Descendants("Permission")
.Where(i => i.Attribute("Name") != null && i.Attribute("Name").Value == "permissionXYZ")
.Select(i => i.Descendants("Field"));