Search code examples
c#xmllinqxpathxelement

List of XPaths on XElement


I am trying to get the value of the first xpath match in my C# -

private string getValue(List<string> xpaths, XElement xml) {
        string stringValue = string.Empty;
        foreach(string xpath in xPaths)
        {
            var value = xml.XPathEvaluate(xpath);

            if (value is IEnumerable)
            {
                foreach (XObject xObject in value)
                {
                    if (xObject is XElement)
                    {
                        stringValue = ((XElement)xObject).Value.FirstOrDefault().ToString();
                        break;
                    }
                    else if (xObject is XAttribute)
                    {
                        stringValue = ((XAttribute)xObject).Value.FirstOrDefault().ToString();
                        break;
                    }
                }
            }
            else
                stringValue = value.ToString();
        }
        return stringValue;

}

My List will look like this -

//Request/Header/Error/text()    
//Request/Header/Error[0]/Details/ErrorMesage/text()
//Request/Header/Error/Details/@code
//Request/Header/Error/Details/StackTrace/text()

In the list of XPaths, I want to go through the list and return the first match. The XPathEvaluate is not working for me. I am not sure what is wrong.

Possible XML values

<Request>
  <Header>
    <Error>
        <Details>
            <StackTrace>trace</StackTrace>
        </Details>
    <Error>
  </Header>
</Request>


 <Request>
  <Header>
    <Error>
        <Details>
            <ErrorMessage>This is the Error Message1.</ErrorMessage>
        </Details>
    <Error>
    <Error>
        <Details>
            <ErrorMessage>This is the Error Message2.</ErrorMessage>
        </Details>
    <Error>
  </Header>
</Request>

<Request>
  <Header>
    <Error>This is the error message.<Error>
  </Header>
</Request>

<Request>
  <Header>
    <Error>
        <Details code="123">
            <StackTrace>trace</StackTrace>
        </Details>
    <Error>
  </Header>
</Request>

<Request>
  <Header>
    <Error>
        <Details>
            <ErrorMessage>This is the Error Message.</ErrorMessage>
        </Details>
    <Error>
  </Header>
</Request>

This is passed to the method like this-

getValue(xpathList, XElement.Parse(xmlString))

Since I dont know what xml I will be getting, I want to cycle through the list of XPaths and return the first match. It can either be a element text or an attribute value.


Solution

  • The first thing, we use XDocument, you can use XElementbut you need to change the XPath expression for xpathList, more details:
    1 - You have an error in all xml test case, Error tag not closed, then correct them:

    <Request>
      <Header>
        <Error>
            <Details>
                <StackTrace>trace</StackTrace>
            </Details>
        </Error>
      </Header>
    </Request>
    

    2 - you have an error in this expression //Request/Header/Error[0]/Details/ErrorMesage/text(), to get the first Error use 1 not 0, you miss alsos in ErrorMesage:

    List<string> xPathList = new List<string>
    {
        "//Request/Header/Error/text()",
        "//Request/Header/Error[1]/Details/ErrorMessage/text()",
        "//Request/Header/Error/Details/@code",
        "//Request/Header/Error/Details/StackTrace/text()"
    };
    

    3 - Change GetValue, like the following code:

    private string GetValue(List<string> xPaths, XDocument xml)
    {
        string stringValue = string.Empty;
        foreach (string xpath in xPaths)
        {
            var value = xml.XPathEvaluate(xpath);
    
            foreach (XObject xObject in (IEnumerable)value)
            {
                if (xObject is XElement)
                {
                    return ((XElement)xObject).Value;
                }
                else if (xObject is XAttribute)
                {
                    return ((XAttribute)xObject).Value;
                }
                else
                {
                    return ((XText)xObject).Value;
                }
            }
        }
    
        return stringValue;
    }
    

    4 - Test

    string result = GetValue(xPathList, XDocument.Parse(xmlString));
    

    5 - Demo:

     string xml1 = @"<Request>
                    <Header>
                        <Error>
                            <Details>
                                <StackTrace>trace</StackTrace>
                            </Details>
                        </Error>
                      </Header>
                </Request>";
    
    string xml2 = @"<Request>
                      <Header>
                        <Error>
                            <Details>
                                <ErrorMessage>This is the Error Message1.</ErrorMessage>
                            </Details>
                        </Error>
                        <Error>
                            <Details>
                                <ErrorMessage>This is the Error Message2.</ErrorMessage>
                            </Details>
                        </Error>
                      </Header>
                    </Request>";
    
    string xml3 = @"<Request>
                      <Header>
                        <Error>This is the error message.</Error>
                      </Header>
                    </Request>";
    
    string xml4 = @"<Request>
                      <Header>
                        <Error>
                            <Details code=""123"">
                                <StackTrace>trace</StackTrace>
                            </Details>
                        </Error>
                      </Header>
                    </Request>";
    
    string xml5 = @"<Request>
              <Header>
                <Error>
                    <Details>
                        <ErrorMessage>This is the Error Message.</ErrorMessage>
                    </Details>
                </Error>
              </Header>
            </Request>";
    
    List<string> xPathList = new List<string>
    {
        "//Request/Header/Error/text()",
        "//Request/Header/Error[1]/Details/ErrorMessage/text()",
        "//Request/Header/Error/Details/@code",
        "//Request/Header/Error/Details/StackTrace/text()"
    };
    
    string result1 = GetValue(xPathList, XDocument.Parse(xml1));
    Console.WriteLine($"xml1 : {result1}");
    
    string result2 = GetValue(xPathList, XDocument.Parse(xml2));
    Console.WriteLine($"xml2 : {result2}");
    
    string result3 = GetValue(xPathList, XDocument.Parse(xml3));
    Console.WriteLine($"xml3 : {result3}");
    
    string result4 = GetValue(xPathList, XDocument.Parse(xml4));
    Console.WriteLine($"xml4 : {result4}");
    
    string result5 = GetValue(xPathList, XDocument.Parse(xml5));
    Console.WriteLine($"xml5 : {result5}");
    

    6 - Result

    xml1 : trace
    xml2 : This is the Error Message1.
    xml3 : This is the error message.
    xml4 : 123
    xml5 : This is the Error Message.
    

    I hope this help