views:

80

answers:

4

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?

+1  A: 

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"));
SLaks
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 Harvey
@Robert: What's in `y.Attributes()`? What are their names?
SLaks
@SLaks: `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 Harvey
@Robert: You need to declare an `XNamespace` for `http://www.w3.org/2001/XMLSchema`, like I did.
SLaks
Thanks for your help. I've posted the code that works.
Robert Harvey
+1  A: 

Try this

elements.DescendantsAndSelf().
                First(y => y.Attribute(XName.Get("name", "http://www.w3.org/2001/XMLSchema")));
saurabh
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.
Robert Harvey
+1  A: 

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.

Gabe
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.
Robert Harvey
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.
Robert Harvey
Robert: Try the expanded name `{http://www.w3.org/2001/XMLSchema}element`
Gabe
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.
Robert Harvey
A: 

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);
}
Robert Harvey
`ns + "element"` should work as well as `ns.GetName("element")`
Gabe
@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.
Robert Harvey
I didn't think that `ns + "element"` would even *work* with a call to `ToString()`, although I suppose it should.
Gabe
@Gabe: No, it should't.
SLaks