tags:

views:

90

answers:

2

I have a collection of MODS records that looks like this:

<modsCollection>
<mods [namespaces etc] >
    <genre authority="diva" type="contentType" lang="eng">Other academic</genre>
    <genre authority="diva" type="contentType" lang="swe">Övrigt vetenskapligt</genre>
    <name type="personal">
      <namePart type="family">Svensson</namePart>
      <namePart type="given">Sven</namePart>
      <namePart type="date">1880-</namePart>
      <role>
        <roleTerm type="code" authority="marcrelator">aut</roleTerm>
      </role>
      <affiliation>Stockholms universitet, institutionen institutionen</affiliation>
    </name>
[...]
</mods>
<mods/>
<mods/>
</modsCollection>

My LINQ query to search the colloction for records affiliated with a certain person with a certain role looks like this:

XElement[] hits = (from record in x.Root.Elements(modsV3 + "mods").Elements(modsV3 + "name")
    from r1 in record.Elements(modsV3+"namePart")
    where  
        r1.HasAttributes && 
        r1.Attribute("type").Value == "family" &&
        r1.Value == familyName
   from r2 in record.Elements(modsV3 + "namePart")
   where
       r2.HasAttributes &&
       r2.Attribute("type").Value == "given" &&
       r2.Value == givenName
   from r3 in record.Elements(modsV3 + "role").Elements(modsV3+"roleTerm")
   where
       r3.HasAttributes &&
       r3.Attribute("type").Value == "code" &&
       r3.Value == "aut"
   select r1.Parent.Parent).ToArray<XElement>();    

I think this query could be better written. How?

+1  A: 

I know you want to use only LINQ, but maybe a mix with XPath simplifies your code:

XElement[] hits = (from record in config.Elements(modsV3 + "mods").Elements(modsV3 + "name")
where   record.XPathSelectElement(string.Format("./{0}namePart[@type='family' and .='{1}']", modsV3, familyName)) != null &&
        record.XPathSelectElement(string.Format("./{0}namePart[@type='given' and .='{1}']", modsV3, givenName)) != null &&
        record.XPathSelectElement(string.Format("./{0}role/{0}roleTerm[@type='code' and .='aut']", modsV3)) != null
select record.Parent).ToArray<XElement>(); 
alex
+2  A: 

I would use the Extension method syntax, and make Linq filter methods like below:

    private static XElement[] GetHits(XDocument x, string modsV3, string givenName, string familyName)
    {

        return x.Root.Elements(modsV3 + "mods")
            .MatchNamePart("given", givenName)
            .MatchNamePart("family", familyName).ToArray();
    }

    private static string modsV3 = "whatever";

    private static IEnumerable<XElement> MatchNamePart(this IEnumerable<XElement> records, string type, string givenName)
    {
        return records.Where(rec => rec.Element(modsV3 + "name").
            Elements(modsV3 + "namePart").Any(r1 => HasAttrib(r1, type, givenName)));
    }

    private static bool HasAttrib(XElement element, string attribName, string value)
    {
        return  element.HasAttributes &&
                element.Attribute("type").Value == attribName &&
                element.Value == value;
    }

This only does the name matching. But you can use these methods as buildingblocks. You can reuse the name part matching wherever you query this type of document, so the non-reusable portion is small. The second half of the query can be derived from this example.

jdv