1

I am working with this XSD file. The portion of the XML that is relevant to this question is here:

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns="https://wsmrc2vger.wsmr.army.mil/rcc/manuals/106-11" 
           targetNamespace="https://wsmrc2vger.wsmr.army.mil/rcc/manuals/106-11"
           elementFormDefault="qualified" 
           attributeFormDefault="unqualified">
    <xs:element name="Tmats">
        <xs:complexType>
            <xs:sequence>
                <xs:annotation>
                    <xs:documentation>TMATS G Group</xs:documentation>
                </xs:annotation>
                <xs:element name="ProgramName" type="xs:string" minOccurs="0">
                    <xs:annotation>
                        <xs:documentation>PN</xs:documentation>
                    </xs:annotation>
                </xs:element>

To get the documentation value for a given xs:element, I have this small function, which recursively walks the descendant nodes until it finds the documentation element:

public string GetCode(XElement e)
{
    foreach (var s in e.Elements())
    {
        // If we hit an intervening element, bail out.
        if (s.Name.ToString().Contains("element"))
            return "";

        if (s.Name.ToString().Contains("annotation"))
        {
            // I'll explain this loop in a moment.
            foreach (var t in s.Elements())
            {
                if (t.Name.ToString().Contains("documentation"))
                    return t.Value;
            }
        } 
        else
            return GetCode(s);
    }
    return "";
}

So far so good. The unit test looks like this:

[TestMethod()]
public void GetCodeTest()
{
    string path = @"C:\Documents and Settings\harvey robert\Desktop\Tmats.xsd";

    IEnumerable<XElement> elements =
        from e in XElement.Load(path).Elements()
        select e;

    TmatsDictionary target = new TmatsDictionary(); 
    XElement x = elements.First();
    string actual = target.GetCode(x);
    Assert.AreEqual("TMATS G Group", actual);
}

Which passes. Now I want to extend the test by adding an additional case, like this:

    XElement z = elements.DescendantsAndSelf()
                         .First(y => y.Attribute("name")
                         .ToString().Contains("ProgramName"));

    actual = target.GetCode(z);
    Assert.AreEqual("PN", actual);

...But this fails due to a null object reference (most likely y.Attribute("name")).

Did you see the loop in the function above that I commented?

// I'll explain this loop in a moment.
foreach (var t in s.Elements())
{
    if (t.Name.ToString().Contains("documentation"))
        return t.Value;
}

It's written that way because I can't figure out how to express the condition in a Lambda statement that works.

Any suggestions?

4 Answers 4

1

You need to use namespaces:

XNamespace ns = "https://wsmrc2vger.wsmr.army.mil/rcc/manuals/106-11";
XElement z = elements.DescendantsAndSelf()
                     .First(y => y.Attribute(ns + "name")
                         .Value.Contains("ProgramName"));
Sign up to request clarification or add additional context in comments.

4 Comments

Tried that, still getting the same error. Actually, it turns out that an older version of the XSD didn't have the namespaces in it, so I don't think it is the namespaces, but thanks for pointing that out. The IDE says that it is failing on .First(y => y.Attribute(ns + "name")
@Robert: What's in y.Attributes()? What are their names?
y.Attributes() is a bit hard to isolate. However, elements.DescendantsAndSelf() returns all elements (not just the xs:element elements), and most of the non-xs:element elements don't have attributes, which must explain the null reference error. I suspect I need to do something like elements.DescendantsAndSelf("xs:element").Attribute("name") ..etc, but if I try that, I get the error, "The character : cannot be included in a name." Grf.
@Robert: You need to declare an XNamespace for http://www.w3.org/2001/XMLSchema, like I did.
1

Try this

elements.DescendantsAndSelf().
                First(y => y.Attribute(XName.Get("name", "http://www.w3.org/2001/XMLSchema")));

1 Comment

That looks like it will work. Is there a way to get it to globally "XNamespace" in the style that SLaks posted, and still get it to recognize the prefix? Your way is a lot of typing if I have to do this in many places.
1

Your problem is that y.Attribute("name").ToString().Contains("ProgramName") will fail on every element that doesn't contain a "name" attribute. You need something like this:

y.Attribute("name") != null &&
y.Attribute("name").ToString().Contains("ProgramName");

If you expect all <element> elements to contain a name attribute, you can ignore the null check and do this:

XElement z = elements.DescendantsAndSelf(
                         "{http://www.w3.org/2001/XMLSchema}element")
                     .First(y => y.Attribute("name") 
                     .ToString().Contains("ProgramName"));

EDIT: Note that I added the expanded name to include the namespace URL. See if that works.

3 Comments

That's true, but I'd rather just filter out everything but the xs:element elements (which all have attributes) but to do that, I need to figure out how to assign an xs prefix to the thing.
Saw your edit. DescendantsAndSelf("element") will only work if I figure out how to use an xs prefix with it. DescendantsAndSelf("xs:element") will not work.
Thanks, Gabe. You were right about the expanded name. I figured out how to get that name from the XNamespace, and I posted the working code here.
0

Here is the code that works.

Notice the call to the GetName() method in the DescendantsAndSelf() call. This returns a properly formatted URI-prefixed name of the form {http://www.w3.org/2001/XMLSchema}element, which will match correctly to the xs:element names.

The result is that DescendantsAndSelf() returns only those elements having the name xs:element, all of which have attributes associated with them (so there is no chance of a null reference error when referencing the Attributes collection).

[TestMethod()]
public void GetCodeTest()
{
    string path = @"C:\TestArea\Tmats_09-2010.xml";

    IEnumerable<XElement> elements =
        from e in XElement.Load(path).Elements()
        select e;

    TmatsDictionary target = new TmatsDictionary();            
    XNamespace ns = "http://www.w3.org/2001/XMLSchema";

    XElement z = elements.DescendantsAndSelf(ns.GetName("element")) 
                         .First(y => y.Attribute("name")
                         .Value.Equals("ProgramName"));

    actual = target.GetCode(z);
    Assert.AreEqual("PN", actual);
}

3 Comments

ns + "element" should work as well as ns.GetName("element")
@Gabe: Possibly, but it doesn't look right without a ToString() call, and that makes it too long. I'll try it tomorrow. Anyway, the GetName() call seems to make more sense to me, even though it takes up a few more characters.
I didn't think that ns + "element" would even work with a call to ToString(), although I suppose it should.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.