How to read a processing instruction from XML string using C# .NET?

Viewed 508

I would like to read a processing instruction from some small well-formed XML chunk, based on this post: How to read processing instruction from an XML file using .NET 3.5

However, it doesn't work. I get the error, that the pi object is null. This is what I do:

XmlDocument doc = new XmlDocument();
doc.LoadXml("<root>Text <?pi 1?>blabla</root>");
Console.WriteLine(doc.ChildNodes[0].Name); // output: root

XmlProcessingInstruction pi = doc.OfType<XmlProcessingInstruction>().Where(x => x.Name == "pi").FirstOrDefault();
Console.WriteLine(pi.Value);

Parsing the XML works. When I get the error (System-NullReferenceException) in Visual Studio, I get it for line "Console.WriteLine(pi.Value);".

Where the error? How do I get/read the processing instruction?

4 Answers

You have two issues in your code.

The first is that it fails on Console.WriteLine(doc.ChildNodes[1].Name);, "root" is the first node, not the second (zero-based). But this is probably a typo.

As to the issue in question:

Enumerable.OfType() is using GetEnumerator() of XmlDocument class which is implemented by it's parent XmlNode.GetEnumerator(). The documentation states:

An IEnumerator object that can be used to iterate through the child nodes in the current node.

But you are trying to run OfType on doc, who has only a single child: root.

If you change your code to run on root's children instead, it will work as expected:

XmlProcessingInstruction pi = doc.ChildNodes[0].OfType<XmlProcessingInstruction>().Where(x => x.Name == "pi").FirstOrDefault();

With XmlDocument:

var doc = new XmlDocument();
doc.Load("test.xml");

// get all instructions
var instructions = doc.SelectNodes("//processing-instruction()");

foreach (XmlProcessingInstruction instruction in instructions)
    Console.WriteLine(instruction.Target + " : " + instruction.Data);

// get concrete instruction
var pi = doc.SelectSingleNode("//processing-instruction('pi')") as XmlProcessingInstruction;
Console.WriteLine(pi.Target + " : " + pi.Data);

LINQ to XML. It's much more convenient and easier:

var xml = XDocument.Load("test.xml");

var instructions = xml.DescendantNodes()
    .OfType<XProcessingInstruction>();

foreach (var instruction in instructions)
    Console.WriteLine(instruction);

var pi = instructions.FirstOrDefault(x => x.Target == "pi");
Console.WriteLine(pi.Data);

I was able to make it work this way:

 static void Main(string[] args)
   {
    XmlDocument doc = new XmlDocument();
    doc.Load("c:\\test\\xmlData.xml");
             
   List<XmlProcessingInstruction> piList = new List<XmlProcessingInstruction>(); 
      foreach (XmlNode cnode in doc.ChildNodes)
       {
         foreach (var ccnode in cnode)
         {
           if (ccnode is XmlProcessingInstruction)
               piList.Add(ccnode as XmlProcessingInstruction);
         }
      }
      Console.WriteLine(piList.FirstOrDefault(a => a.Name == "pi").Value);
   }

xmlData.xml

   <?xml version="1.0" encoding="utf-8"?>
     <root>
     <?pi 1?>
       <value>6</value>
     </root>

I believe the reason you couldn't get pi was that you didn't have quotes around your Pi target and value. I could not get it to work as a string, but it worked fine reading from a file.

{
    private float _length;
    private int _toeCount;


    public Socks()
    { //code here;
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Socks arg)) return false;

        return (this._length.Equals(arg._length));
    }

    public override int GetHashCode()
    {
        return 1;
    }

    public static int SortByWidthToe(Socks a, Socks b)
    {
        if (a == null && b == null) return 0;
        if (a == null) return -1;
        if (b == null) return 1;

        if (a._length.Equals(b._length))
            return a._toeCount.CompareTo(b._toeCount);

        return a._length.CompareTo(b._length);
    }
}
Related