I recently started to work with SOAP.
Right now I am trying to parse SOAP message in C#.
Message is as it follows:
<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<ns1:getBuildingsResponse xmlns:ns1="http://someserver.net/~username/lab/servis?ws=1">
<return SOAP-ENC:arrayType="ns2:Map[2]" xsi:type="SOAP-ENC:Array" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">code</key>
<value xsi:type="xsd:string">345-GESG</value>
</item>
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">Building 1</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">7590913</value>
</item>
<item>
<key xsi:type="xsd:string">code</key>
<value xsi:type="xsd:string">353-gr</value>
</item>
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">Building 2</value>
</item>
</item>
</return>
I want to extract values of keys id,code and name.
I tried doing something like this:
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(client.Invoke("getBuildings").ToString());
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
nsmgr.AddNamespace("i", "item");
XmlNodeList xNodelst = xdoc.DocumentElement.SelectNodes("item", nsmgr);
Console.WriteLine(xNodelst.Count);
foreach (XmlNode xn in xNodelst)
{
Console.WriteLine(xn.Value);
}
The problem is, I don't know how to act with tags that have no namespace...
This line of code:
Console.WriteLine(xNodelst.Count);
always prints 0, but I want it to print 2, since I have 2 elements in array (ns2:Map[2]).
Meaning, I want to loop through all of these elements:
<item xsi:type="ns2:Map">
Any help will be appreciated.
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(client.Invoke("getBuildings").ToString());
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
XmlNodeList nodes = xDoc.SelectNodes("//item[#xsi:type='ns2:Map']",nsmgr);
var nodeCount=nodes.Count;
try this, this might help you.
Related
I need deserialize XML file "c:\Temp\Des.xml":
<return xsi:type="ns2:Map"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item>
<key xsi:type="xsd:int">218980</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">218980</value>
</item>
<item>
<key xsi:type="xsd:string">title</key>
<value xsi:type="xsd:string">Product Title 1</value>
</item>
<item>
<key xsi:type="xsd:string">price</key>
<value xsi:type="xsd:string">10.30</value>
</item>
<item>
<key xsi:type="xsd:string">images</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
<value xsi:type="xsd:string">https://test.com/Image1.jpg</value>
</item>
<item>
<key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
<value xsi:type="xsd:string">https://test.com/Image2.jpg</value>
</item>
</value>
</item>
<item>
<key xsi:type="xsd:string">specifications</key>
<value SOAP-ENC:arrayType="ns2:Map[2]" xsi:type="SOAP-ENC:Array"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">name1</value>
</item>
<item>
<key xsi:type="xsd:string">value</key>
<value xsi:type="xsd:string">value1</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">name2</value>
</item>
<item>
<key xsi:type="xsd:string">value</key>
<value xsi:type="xsd:string">value2</value>
</item>
</item>
</value>
</item>
</value>
</item>
<item>
<key xsi:type="xsd:int">218981</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">218981</value>
</item>
<item>
<key xsi:type="xsd:string">title</key>
<value xsi:type="xsd:string">Product Title 2</value>
</item>
<item>
<key xsi:type="xsd:string">price</key>
<value xsi:type="xsd:string">10.40</value>
</item>
<item>
<key xsi:type="xsd:string">images</key>
<value xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
<value xsi:type="xsd:string">https://test.com/Image4.jpg</value>
</item>
<item>
<key xsi:type="xsd:string">231d314ae3f1df4d56bf267fb194c537</key>
<value xsi:type="xsd:string">https://test.com/Image5.jpg</value>
</item>
</value>
</item>
<item>
<key xsi:type="xsd:string">specifications</key>
<value SOAP-ENC:arrayType="ns2:Map[2]" xsi:type="SOAP-ENC:Array"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">name12</value>
</item>
<item>
<key xsi:type="xsd:string">value</key>
<value xsi:type="xsd:string">value12</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">name22</value>
</item>
<item>
<key xsi:type="xsd:string">value</key>
<value xsi:type="xsd:string">value22</value>
</item>
</item>
</value>
</item>
</value>
</item>
</return>
I created class for that
[Serializable()]
[XmlRoot("Item", Namespace = "", IsNullable = false)]
public class Item
{
[XmlElement("id")]
public int Id { get; set; }
[XmlElement("title")]
public string Title { get; set; }
[XmlElement("price")]
public decimal Price { get; set; }
[XmlElement("images")]
public string[] Images { get; set; }
[XmlElement("specifications")]
public object Specifications { get; set; }
}
and call Deserialize:
public class Program
{
static void Main(string[] args)
{
string xml = File.ReadAllText("c:\\Temp\\Des.xml");
StringReader stringReader = new StringReader(xml);
XmlSerializer serializer = new XmlSerializer(typeof(List<Item>), new XmlRootAttribute("return"));
List<Item> items = (List<Item>) serializer.Deserialize(stringReader);
but items are always empty list, what I'm doing wrong? Thank you!
As #jle said here:
I don't think attributes will work because of the key/value structure.
There is no way for a program to infer from the XML alone what
properties an object has. I would make a static extension method
helper function to get the values:
you can use the below extension method for getting the value of each key:
public static class XmlHelper
{
public static string GetValueByKeyName(this XElement element, string key)
{
return element.Descendants("key")
.First(v => v.Value == key)
.ElementsAfterSelf("value")
.First()
.Value;
}
}
and then, your code became like that:
var ItemObjects = new List<Item>();
XDocument doc = XDocument.Load(#"D:/file.xml");
var elements = doc.Root.Elements("item").ToList();
elements.ForEach(item => {
ItemObjects.Add(new Item {
Id = int.Parse(item.GetValueByKeyName("id")),
Title = item.GetValueByKeyName("title"),
Price = decimal.Parse(item.GetValueByKeyName("price"), CultureInfo.InvariantCulture),
//other props
});
});
I test it with your data, and it works properly. good luck.
Ok, I found solution, in case anyone need it, I use Linq to XML for that:
var items = (from element in xdoc.Descendants("return").Elements("item")
select new
{
Id = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "id").Elements("value").First().Value,
Title = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "title").Elements("value").First().Value,
Price = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "price").Elements("value").First().Value,
Images = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "images").Elements("value").Elements("item").Elements("value").Select(i => i.Value).ToList(),
Specifications = element.Elements("value").Elements("item").Where(c => c.Element("key").Value == "specifications").Elements("value").Elements("item").Select(s =>
new KeyValuePair<string, string>
(
s.Elements("item").First().Elements("value").First().Value,
s.Elements("item").First().Elements("value").Last().Value
)
).ToList(),
}
).ToList();
I would like to parse the below xml using XDocument in Linq.
<?xml version="1.0" encoding="UTF-8"?>
<string xmlns="http://tempuri.org/">
<Sources>
<Item>
<Id>1</Id>
<Name>John</Name>
</Item>
<Item>
<Id>2</Id>
<Name>Max</Name>
</Item>
<Item>
<Id>3</Id>
<Name>Ricky</Name>
</Item>
</Sources>
</string>
My parsing code is :
var xDoc = XDocument.Parse(xmlString);
var xElements = xDoc.Element("Sources")?.Elements("Item");
if (xElements != null)
foreach (var source in xElements)
{
Console.Write(source);
}
xElements is always null. I tried using namespace as well, it did not work. How can I resolve this issue?
Try below code:
string stringXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><string xmlns=\"http://tempuri.org/\"><Sources><Item><Id>1</Id><Name>John</Name></Item><Item><Id>2</Id><Name>Max</Name></Item><Item><Id>3</Id><Name>Ricky</Name></Item></Sources></string>";
XDocument xDoc = XDocument.Parse(stringXml);
var items = xDoc.Descendants("{http://tempuri.org/}Sources")?.Descendants("{http://tempuri.org/}Item").ToList();
I tested it and it correctly shows that items has 3 lements :) Maybe you used namespaces differently (it's enough to inspect xDoc objct in object browser and see its namespace).
You need to concatenate the namespace and can directly use Descendants method to fetch all Item nodes like:
XNamespace ns ="http://tempuri.org/";
var xDoc = XDocument.Parse(xmlString);
var xElements = xDoc.Descendants(ns + "Item");
foreach (var source in xElements)
{
Console.Write(source);
}
This prints on Console:
<Item xmlns="http://tempuri.org/">
<Id>1</Id>
<Name>John</Name>
</Item><Item xmlns="http://tempuri.org/">
<Id>2</Id>
<Name>Max</Name>
</Item><Item xmlns="http://tempuri.org/">
<Id>3</Id>
<Name>Ricky</Name>
</Item>
See the working DEMO Fiddle
I am trying to edit a specific node in an XML document. I have the following XML data:
<Items xmlns="http://foo.com/blah/blah">
<Item>
<Format>1</Format>
<Name>Edit this one</Name>
<MetaDataDictionary>
<MetaData>
<Name>Do not edit this one</Name>
<Value>0</Value>
</MetaData>
</MetaDataDictionary>
</Item>
<Item>
<Format>1</Format>
<Name>Edit this one</Name>
<MetaDataDictionary>
<MetaData>
<Name>Do not edit this one</Name>
<Value>0</Value>
</MetaData>
</MetaDataDictionary>
</Item>
</Items>
I want to append a number to each Item/Name node content but not to the Metadata/Name nodes, save the file off as test_n.xml and repeat n number of times.
The code I'm using seems to get me what I want for the Item/Name node and saved the file(s) correctly but it also updates the Metadata/Name nodes and I do not want that value to be updated. I understand the problem is in the navigator.Select call but I just don't know how to update one and skip the other.
XmlDocument doc = new XmlDocument();
doc.Load("test.xml");
XPathNavigator navigator = doc.CreateNavigator();
XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
manager.AddNamespace("at", "http://foo.com/blah/blah");
for (int i = 0; i < 10; i++)
{
foreach (XPathNavigator nav in navigator.Select("//at:Name", manager))
{
var currValue = nav.Value;
nav.SetValue(currValue + " " + i);
}
doc.Save("test_" + i + ".xml");
}
In the end I'd like it to be:
<Items xmlns="http://foo.com/blah/blah">
<Item>
<Format>1</Format>
<Name>Edit this one 0</Name>
<MetaDataDictionary>
<MetaData>
<Name>Do not edit this one</Name>
<Value>0</Value>
</MetaData>
</MetaDataDictionary>
</Item>
<Item>
<Format>1</Format>
<Name>Edit this one 0</Name>
<MetaDataDictionary>
<MetaData>
<Name>Do not edit this one</Name>
<Value>0</Value>
</MetaData>
</MetaDataDictionary>
</Item>
</Items>
Don't select all name tags, instead select only name tags which are children of /Items/Item tag.
The XPath query would look like /Items/Item/Name.
You can test it here. See the documentation for details.
I'm trying to select a specific node and fetch the values in it's childnodes. This would normally be pretty easy, but the complication is that the nodes have the same name. My xml looks something like this;
<Settings>
<Config>
</Config>
<Items>
<Item>
<ID>Hello</ID>
<Pth>Somevalue</Pth>
<Zvb>True</Zvb>
<Ico>True</Ico>
</Item>
<Item>
<ID>Stack</ID>
<Pth>Somevalue</Pth>
<Zvb>False</Zvb>
<Ico>True</Ico>
</Item>
<Item>
<ID>Overflow</ID>
<Pth>Somevalue</Pth>
<Zvb>False</Zvb>
<Ico>True</Ico>
</Item>
</Items>
</Settings>
Each <ID>'s innertext is always unique. I now want to select the <Item> ,where it's <ID>'s innertext is "Stack". (I need the other childnode-values as well, like Pth, Zvb and Ico. So everything under <Item> basically)
I did this is powershell, and it looks something like this;
$script:specificItem = $dgvItems.rows[$_.RowIndex].Cells[1].Value
$script:fetch = #($xml.SelectNodes('//Item')) | Select-Object * | Where { $_.ID -like $specificItem }
So far I've got this (I'm in a RowEnter event of a datagridview):
XmlDocument xml = new XmlDocument();
xml.Load(GlobalVars.configfile);
int rowindex = dgvItemlist.CurrentCell.RowIndex;
dgvItemlist.Rows[rowindex].Cells[2].Value.ToString(); //This will contain for example "Stack"
XmlNodeList Items = xml.SelectNodes("//Items/Item"); //probably other ways to start as well
... but from here I struggle with filtering or selecting the one I want. I know this is a fairly common question, but I can't find a good solution for this exact issue.
You could also use XDocument (Linq to XML):
string xml =#"<Settings>
<Config>
</Config>
<Items>
<Item>
<ID>Hello</ID>
<Pth>Somevalue</Pth>
<Zvb>True</Zvb>
<Ico>True</Ico>
</Item>
<Item>
<ID>Stack</ID>
<Pth>Somevalue</Pth>
<Zvb>False</Zvb>
<Ico>True</Ico>
</Item>
<Item>
<ID>Overflow</ID>
<Pth>Somevalue</Pth>
<Zvb>False</Zvb>
<Ico>True</Ico>
</Item>
</Items>
</Settings>";
XDocument xdoc = XDocument.Parse(xml);
XElement desired = xdoc.Descendants("Item").FirstOrDefault(x=>(string)x.Element("ID")=="Stack");
if(desired!=null)
{
string Pth = (string)desired.Element("Pth");
string Zvb = (string)desired.Element("Zvb");
string Ico = (string)desired.Element("Ico");
}
desired will be the wanted element.
Try to change the last line of your code into:
XmlNodeList Items = xml.SelectNodes("//Items/Item[ID='Stack']");
This should return:
<Item>
<ID>Stack</ID>
<Pth>Somevalue</Pth>
<Zvb>False</Zvb>
<Ico>True</Ico>
</Item>
Try the following. It will return the specific node you are looking for.
XmlNode itemNode = doc.SelectSingleNode("//ID[text()='Stack']").ParentNode;
I get a soap response from the service:
<SOAP-ENV:Body>
<ns1:getCurrencyListResponse>
<getCurrencyListReturn SOAP-ENC:arrayType="ns2:Map[2]" xsi:type="SOAP-ENC:Array">
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">18</value>
</item>
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">USD</value>
</item>
</item>
<item xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">id</key>
<value xsi:type="xsd:string">19</value>
</item>
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">EUR</value>
</item>
</item>
</getCurrencyListReturn>
</ns1:getCurrencyListResponse>
</SOAP-ENV:Body>
File wsdl does not specify a return type. How can I deserializing this messages in C#? The number of "item" and "Map" is not know. Should be similar to this example:
[SoapType(Namespace = "http://xml.apache.org/xml-soap", TypeName = "Map")]
public class Map
{
public item[] item;
}
public class item
{
[SoapElement]
public string key;
[SoapElement]
public string value;
}
In your project you should add a reference to webservice.
In this way a wrapper for webservice is automatically created, letting you call its methods and getting responses using classes inside wrapper.
Serialization and deserialization are transparent to you.