2011/04/06

[C#][XPath] 模擬 XPath 的 max 與 min

在 XPath 中, 有兩個函式感覺會很常用, 但 C# 中卻沒 support. 那就是 max() 和 min().
少了這兩個函式, 每次想找 xml 資料中的最大/最小值, 就只能用 Linq 或是 Select 出所有標籤後, 在程式中去一個個比對.
其實 XPath 只要轉個彎, 就能做出類似的功能.
以下用一個簡單的 xml 來說明如何找出各班成績最好/最差的 <Student>:
<?xml version="1.0" encoding="UTF-8"?>
<ClassSet>
    <Class>
        <Name>Class_A</Name>
        <Student>
            <Name>John</Name>
            <Score>87</Score>
        </Student>
        <Student>
            <Name>Mary</Name>
            <Score>85</Score>
        </Student>
        <Student>
            <Name>Apple</Name>
            <Score>100</Score>
        </Student>
        <Student>
            <Name>May</Name>
            <Score>60</Score>
        </Student>
    </Class>
    <Class>
        <Name>Class_B</Name>
        <Student>
            <Name>Peter</Name>
            <Score>100</Score>
        </Student>
        <Student>
            <Name>Rex</Name>
            <Score>100</Score>
        </Student>
        <Student>
            <Name>Kelly</Name>
            <Score>60</Score>
        </Student>
        <Student>
            <Name>Hebe</Name>
            <Score>58</Score>
        </Student>
    </Class>
    <Class>
        <Name>Class_C</Name>
        <Student>
            <Name>Sam</Name>
            <Score>66</Score>
        </Student>
        <Student>
            <Name>Lulu</Name>
            <Score>89</Score>
        </Student>
        <Student>
            <Name>Tom</Name>
            <Score>94</Score>
        </Student>
        <Student>
            <Name>Gina</Name>
            <Score>67</Score>
        </Student>
    </Class>
</ClassSet>
  • 找出各班成績最好的學生:
    概念很簡單: 在同一個班級中, 對每個 <Student>來說, 檢查除了自己以外, 有沒有別人的成績比自己好. 也就是: 我的成績要比別人高, 而且不會有人的成績高過我.
    XmlDocument doc = new XmlDocument();
    doc.Load(@"c:\class_score.xml");
    XmlNodeList lstNode = doc.SelectNodes("//Class/Student["
        + "position()!=../Student[position()] "
        + "and (Score > ../Student/Score and not(Score < ../Student/Score))]");
    
    foreach (XmlNode node in lstNode)
    {
        Console.WriteLine(node.ParentNode["Name"].InnerText);
        Console.WriteLine(node.OuterXml);
    }
    我將判斷式拆成三行來說明:
    1. //Class/Student : 這應該比較沒問題, 只是找出所有 <Class> 下的 <Student>.
    2. position()!=../Student[position()] : 這一段是利用 position() 函式, 來濾掉自己以外的 <Student>.
    3. (Score > ../Student/Score and not(Score < ../Student/Score)) : 這段就是剛上述的, 比較 <Score> 的值, 不僅自己要比別人的 <Score> 高, 且自己不能比別人的 <Score> 低.
    此執行結果如下: (因為 Class_B 有兩位 100 分, 所以會查出兩筆資料)
    Class_A
    <Student><Name>Apple</Name><Score>100</Score></Student>
    Class_B
    <Student><Name>Peter</Name><Score>100</Score></Student>
    Class_B
    <Student><Name>Rex</Name><Score>100</Score></Student>
    Class_C
    <Student><Name>Tom</Name><Score>94</Score></Student>
  • 相同的邏輯, 我們只要將 < 改成 >, > 改成 <, 就可以找出成績最差的資料:
    XmlNodeList lstNode = doc.SelectNodes("//Class/Student["
        + "position()!=../Student[position()] "
        + "and (Score < ../Student/Score and not(Score > ../Student/Score))]");
    執行結果如下:
    Class_A
    <Student><Name>May</Name><Score>60</Score></Student>
    Class_B
    <Student><Name>Hebe</Name><Score>58</Score></Student>
    Class_C
    <Student><Name>Sam</Name><Score>66</Score></Student>
上述的兩個 XPath, 就可以幫我們找出各班級成績最好與最差的學生; 如果我們想找的不分班級, 成績最好與最差的學生呢?
這時只要將查詢句中的 ../Student 改成 //Class/Student 就可以了. (參考如下)
  • 找出成績最好的學生:
    XmlNodeList lstNode = doc.SelectNodes("//Class/Student["
        + "position()!=//Class/Student[position()] "
        + "and (Score > //Class/Student/Score and not(Score < //Class/Student/Score))]");
    執行結果:
    Class_A
    <Student><Name>Apple</Name><Score>100</Score></Student>
    Class_B
    <Student><Name>Peter</Name><Score>100</Score></Student>
    Class_B
    <Student><Name>Rex</Name><Score>100</Score></Student>
  • 找出成績最差的學生:
    XmlNodeList lstNode = doc.SelectNodes("//Class/Student["
        + "position()!=//Class/Student[position()] "
        + "and (Score < //Class/Student/Score and not(Score > //Class/Student/Score))]");
    執行結果:
    Class_B
    <Student><Name>Hebe</Name><Score>58</Score></Student>
* 網路上還有一種用 preceding-sibling / following-sibling 去判斷 max / min 的方式, 但缺點是只能查同一層下的資料, 跨節點的部分不容易做.

沒有留言: