3

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?

capfan
  • 817
  • 10
  • 26
  • 1
    this does not look like a valid xml. – Bizhan May 21 '20 at 19:35
  • it is well-formed XML. Does it have to be valid in order to access the PIs? When I inspect the XmlDocument object, it shows a successful parse tree. – capfan May 21 '20 at 20:09
  • it is not a valid XML or at least it is not a valid processing instruction. try `doc.LoadXml("")` – Bizhan May 21 '20 at 22:21
  • There is a difference between "valid XML" and "well-formed XML". Please cf. https://en.wikipedia.org/wiki/Well-formed_document for an explanation. The code above uses well-formed XML. It's not wrong. I also tried to change the code and add the tag at the beginning and a child element (just to be sure), but no change. Your example code also doesn't contain a processing instruction (cf. https://en.wikipedia.org/wiki/Processing_Instruction). – capfan May 22 '20 at 00:03

4 Answers4

4

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();
xCliede
  • 143
  • 1
  • 9
  • 1
    Also, may I suggest a debugging strategy, the one I used to find your issue: In your case, it would appear that `pi` being null is the problem. But this is only because of `FirstOrDefault()` which will return null in case it has no first. If you break down the expression into seperate variables and inspect each step, you will find that `doc.OfType()` returns an empty enumerable. Every time I see a Linq expression that doesn't do what I want, I first break it apart to make sure I didn't miss anything. – xCliede Dec 23 '20 at 00:21
  • 1
    Thank you. Yes, the index was a typo. And I like the debug strategy. – capfan Dec 27 '20 at 15:39
0

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);
Alexander Petrov
  • 13,457
  • 2
  • 20
  • 49
0

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.

wesreitz
  • 79
  • 6
-3
{
    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);
    }
}
lalu
  • 1