user3456466
user3456466

Reputation:

find an element that has an attribute that has a specific value c#

I have the following Xml file

<?xml version="1.0" encoding="UTF-8"?>
<session xmlns="http://winscp.net/schema/session/1.0" name="test" start="2014-04-04T15:54:09.728Z">
  <upload>
    <filename value="D:\ftp\test1.TXT" />
    <destination value="/in/test1.TXT" />
    <result success="true" />
  </upload>
  <touch>
    <filename value="/in/test1.TXT" />
    <modification value="2014-03-27T12:45:20.000Z" />
    <result success="true" />
  <upload>
    <filename value="D:\ftp\test2.TXT" />
    <destination value="/in/test2.TXT" />
    <result success="true" />
  </upload>
  <touch>
    <filename value="/in/test2.TXT" />
    <modification value="2014-03-27T12:45:20.000Z" />
    <result success="false" />
  </touch>
</session>

I need to and I'd like to browse all filename elements where result success="true" for both nodes touch and upload .

I will get only D:\ftp\test1.TXT

So this is my code :

string file =@"C:\\Program.xml";
            if (File.Exists(file))
            {
                try
                {
                    XElement root = XElement.Load(file);
                    IEnumerable<XElement> filename =
                        from el in root.Elements("upload")
                        where (string)el.Attribute("result success") == "true"
                        select el;
                    foreach (XElement el in filename)
                        Console.WriteLine(el);
                 }
              }

How can I modify the code above to get my purpose?

Upvotes: 0

Views: 94

Answers (4)

Hamlet Hakobyan
Hamlet Hakobyan

Reputation: 33381

Try this:

var doc = XDocument.Parse(file);
XNamespace ns = "http://winscp.net/schema/session/1.0";
var elements = doc.Descendants(ns + "upload")
                  .Where(e =>
                     (string)(e.Element(ns + "result")
                               .Attribute("success")) == "true");

UPDATE

Since requirements changed here is updated solution:

string xml = @"<?xml version='1.0' encoding='UTF-8'?>
<session xmlns='http://winscp.net/schema/session/1.0' name='test' start='2014-04-04T15:54:09.728Z'>
  <upload>
    <filename value='D:\ftp\test1.TXT' />
    <destination value='/in/test1.TXT' />
    <result success='true' />
  </upload>
  <touch>
    <filename value='/in/test1.TXT' />
    <modification value='2014-03-27T12:45:20.000Z' />
    <result success='true' />
  </touch>
  <upload>
    <filename value='D:\ftp\test2.TXT' />
    <destination value='/in/test2.TXT' />
    <result success='true' />
  </upload>
  <touch>
    <filename value='/in/test2.TXT' />
    <modification value='2014-03-27T12:45:20.000Z' />
    <result success='false' />
  </touch>
</session>";

var doc = XDocument.Parse(xml);
XNamespace ns = "http://winscp.net/schema/session/1.0";
var fileNames = doc.Descendants(ns + "result")
    .Where(r => (string)r.Attribute("success") == "true")
    .Select(e => (string)e.ElementsBeforeSelf(ns + "filename").Single().Attribute("value"));

Single used for some scheme validation. In order, if there are no exact one filename in each node touch or upload node you will get exception.

Upvotes: 0

Hogan
Hogan

Reputation: 70523

After the update:

void Main()
{
  XElement root = XElement.Parse (
  @"<?xml version='1.0' encoding='UTF-8'?>
<session  name='test' start='2014-04-04T15:54:09.728Z'>
  <upload>
    <filename value='D:\ftp\test1.TXT' />
    <destination value='/in/test1.TXT' />
    <result success='true' />
  </upload>
  <touch>
    <filename value='/in/test1.TXT' />
    <modification value='2014-03-27T12:45:20.000Z' />
    <result success='true' />
  </touch>
  <upload>
    <filename value='D:\ftp\test2.TXT' />
    <destination value='/in/test2.TXT' />
    <result success='true' />
  </upload>
  <touch>
    <filename value='/in/test2.TXT' />
    <modification value='2014-03-27T12:45:20.000Z' />
    <result success='false' />
  </touch>
</session>");

  var upload = from el in root.Elements("upload") select el;
  var touch = from el in root.Elements("touch") select el;

  // use zip to join the two lists together based on ordering to a new object
  // this WON'T work if the lists are different lengths!
  var filename = upload.Zip(touch,(u,t) => new { upload = u, touch = t })
        .Where(item => item.upload.Descendants("result").First().Attribute("success").Value  == "true" 
                   &&  item.touch.Descendants("result").First().Attribute("success").Value  == "true")
        .Select(item => item.upload.Descendants("filename").First().Attribute("value").Value);


  foreach (string el in filename)
    Console.WriteLine(el);

}

NB, I took out the namespace on the XML content to make it clearer. Feel free to put it back in. (You will have to prefix your names with the namespace if you do.)

Also, I did this in Linq because it was asked for, I think it would be faster to use a for loop over the upload and touch arrays.

Here is how you would do that:

  var uploada = upload.ToArray();
  var toucha = touch.ToArray();
  List<string> filename = new List<string>();

  for(int index = 0; index < uploada.Length  ; index++)
  {
    if (uploada[index].Descendants("result").First().Attribute("success").Value  == "true" 
        && toucha[index].Descendants("result").First().Attribute("success").Value  == "true")
      filename.Add(uploada[index].Descendants("filename").First().Attribute("value").Value);
  }

This worked for me:

void Main()
{
  XElement root = XElement.Parse (
  @"<?xml version=""1.0"" encoding=""UTF-8""?>
<session  name=""test"" start=""2014-04-04T15:54:09.728Z"">
  <upload>
    <filename value=""D:\ftp\test1.TXT"" />
    <destination value=""/in/test1.TXT"" />
    <result success=""true"" />
  </upload>
  <touch>
    <filename value=""/in/test2.TXT"" />
    <modification value=""2014-03-27T12:45:20.000Z"" />
    <result success=""true"" />
  </touch>
</session>");

  var filename  = from el in root.Elements("upload")
                  where el.Descendants("result").First().Attribute("success").Value  == "true"
                  select el.Descendants("filename").First().Attribute("value").Value;


  Console.WriteLine(filename);

}

NB, I took out the namespace on the XML content to make it clearer. Feel free to put it back in. (You will have to prefix your names with the namespace if you do.)

Upvotes: 1

Daniel Br&#252;ckner
Daniel Br&#252;ckner

Reputation: 59645

I would probably use XPath because it yields cleaner code.

var filenamesXPath = "/session/*[result[@success='true']]/filename";

var filenames = document.XPathSelectElements(filenamesXPath);

But this will not just work because of the missing namespace handling - what you actually need is the following.

var document = XDocument.Load(file);

var namespaces = new XmlNamespaceManager(new NameTable());

namespaces.AddNamespace("ns", document.Root.GetDefaultNamespace().NamespaceName);

var filenamesXPath = "/ns:session/*[ns:result[@success='true']]/ns:filename";

var filenames = document.XPathSelectElements(filenamesXPath, namespaces);

Upvotes: 0

Curtis Rutland
Curtis Rutland

Reputation: 786

var xe = XElement.Parse(xml);
var ns = xe.Name.Namespace;
var filenames = from d in xe.Elements()
                let success =  d.Element(ns + "result").Attribute("success")
                where success != null & success.Value == "true"
                select d.Element(ns + "filename").Attribute("value").Value;
filenames.Dump();

This should return you a list of filenames that have a result "true".

Upvotes: 0

Related Questions