views:

1464

answers:

3

I need to count the number of elements in an XML file that have a particular value (to verify uniqueness). The XML file looks like this:

EDIT: I updated the original "simplified" XML with the actual hairy mess someone designed. Unfortunately, this is going to make all the previous Answers really confusing and wrong unless edited.

<root>
  <ac>
   <Properties>
     <Property Name="Alive">
      <Properties>
        <Property Name="ID">
         <Properties>
           <Property Name="Value">
            <long>11007</long>
           </Property>
         </Properties>
        </Property>
      </Properties>
     </Property>
     <Property Name="Dead">
      <Properties>
        <Property Name="ID">
         <Properties>
           <Property Name="Value">
            <long>11008</long>
           </Property>
         </Properties>
        </Property>
      </Properties>
     </Property>
     ...
     <Property Name="MostlyDeadAllDay">
      <Properties>
        <Property Name="ID">
         <Properties>
           <Property Name="Value">
            <long>99001</long>
           </Property>
         </Properties>
        </Property>
      </Properties>
     </Property>
   </Properties>
  </ac>
</root>

I am trying to define a variable to see how many of the properties at the Alive/Dead level have the long value (ID) as defined in a template parameter. Something along these lines (though I suspect this is wrong)...

<xsl:param name="parPropId"/>
<xsl:variable name="countProperties">
   <xsl:value-of select="count(/root/ac/
      Properties/Property/
      Properties/Property[@Name = 'ID']/
      Properites/Property[@Name = 'Value']/long = $parPropId)"/>
</xsl:variable>

There can be multiple Property elements defined at the "ID" level. But I am fairly certain I can count on just one Property element ("Value") under "ID", and only one "long" element under "Value".

[disclaimer] Whoever designed the overall XML file I'm stuck working with REALLY didn't know how to structure XML (e.g., backwards use of attributes versus elements). I fear my XSLT thinking has temporarily been warped :) as a result. [/disclaimer]

A: 
<xsl:variable name="count" select="count(/Property/long = $parPropId)"/>

Un-tested but I think that should work. I'm assuming the Property nodes are direct children of the root node and therefor taking out your descendant selector for peformance

Nick Allen - Tungle139
+2  A: 

Your xpath is just a little off:

count(//Property/long[text()=$parPropId])

Edit: Cerebrus quite rightly points out that the code in your OP (using the implicit value of a node) is absolutely fine for your purposes. In fact, since it's quite likely you want to work with the "Property" node rather than the "long" node, it's probably superior to ask for //Property[long=$parPropId] than the text() xpath, though you could make a case for the latter on readability grounds.

What can I say, I'm a bit tired today :)

annakata
Thanks, @annakata! Phew, I'm exhausted after a full day's work too... Will need to take a break from SO. :P
Cerebrus
Guess what! You have 666 answers now! Significant!
Cerebrus
And entirely coincidentally Dimitre's back on his crusade.
annakata
And not coincindentally, Annakata again deletes any comment that explains what is really wrong with his/her answer.
Dimitre Novatchev
Yes, I really do suffer from a problem of not providing 200 line responses which fail to cover those 0.01% edge cases, just in case the questioner isn't capable of intelligent analysis. You're a hero Dimitree.
annakata
Sometimes your answers fail more badly than not covering 0.01% edge cases annakata. Your biggest failure is you don't accept criticism and don't use it to improve. I'll react always if there is a problem in any answer. Answering isn't a hasty job to collect rep. Erasing this thorny comment? :)
Dimitre Novatchev
You're accusing me of rep whorage? You? You profile indicates 25% of your votes are down - a large number of them against me - which are intended to indicate when an answer is outright wrong or misleading, which this certainly isn't. You blatantly use downs to push your answers up...
annakata
...and post trivial but verbose updates hours after the fact. Your manner has been consistently aggressive, even offensive, and I feel you predictably go out of your way to find and downvote my answers. In short, I find you a hypocrite and a bully, and on balance SO isn't worth the aggro you cause.
annakata
@annakata A simple but really significant achievement is being able to say: "I was wrong. I stand corrected". This requires to be just a little bit brave and noble. I strongly believe that you can reach that stage. Just slow down, don't delete this inconvenient comment and think about it.
Dimitre Novatchev
Pot. Kettle. Black. Your pile of noble downvotes say you have a problem with other people's opinions. Tell you what Dimitre, do something constructive with your life and just walk away. Just walk away. Let's just ignore each other, and let it slide.
annakata
@annakata I will always downvote incorrect answers, even if they happen to be yours! As for a "pile of downvotes", you really overestimate me. Here's a person I deeply respect who has 44% downvotes. http://stackoverflow.com/users/14860/paxMore important than his 25K rep is his fight for truth.
Dimitre Novatchev
+5  A: 

This XPath:

count(//Property[long = '11007'])

returns the same value as:

count(//Property/long[text() = '11007'])

...except that the first counts Property nodes that match the criterion and the second counts long child nodes that match the criterion.

As per your comment and reading your question a couple of times, I believe that you want to find uniqueness based on a combination of criteria. Therefore, in actuality, I think you are actually checking multiple conditions. The following would work as well:

count(//Property[@Name = 'Alive'][long = '11007'])

because it means the same thing as:

count(//Property[@Name = 'Alive' and long = '11007'])

Of course, you would substitute the values for parameters in your template. The above code only illustrates the point.

EDIT (after question edit)


You were quite right about the XML being horrible. In fact, this is a downright CodingHorror candidate! I had to keep recounting to keep track of the "Property" node I was on presently. I feel your pain!

Here you go:

count(/root/ac/Properties/Property[Properties/Property/Properties/Property/long = $parPropId])

Note that I have removed all the other checks (for ID and Value). They appear not to be required since you are able to arrive at the relevant node using the hierarchy in the XML. Also, you already mentioned that the check for uniqueness is based only on the contents of the long element.

Cerebrus
+1 nice to see someone isn't just skim reading :)
annakata
Thanks for educating me on this xpath equivalency! Everything you guys are saying makes perfect sense (based on my moderate XSL/XPath experience). Actually, I don't have multiple criteria, the ID is all I care about. Just can't get that count to give a non-zero value [arrrgghh].
e-holder
Amazingly, these jokers sometimes use 'string' and 'int' instead of just 'long' for these IDs. You've got me motivated to submit to CodingHorror! I wound up removing the '/long' portion of your solution (which now works). I'm flagging this as THE answer, and thanks a million for bearing with me.
e-holder
Thanks, @edholder! Glad to be able to assist.:-)
Cerebrus