11/23/11

Tiết kiệm thời gian và mã lệnh với XPath 2.0 và XSLT 2.0

Tóm tắt:  Ba tính năng thú vị mới trong XPath 2.0 và XSLT 2.0 là kiểu dữ liệu item (mục), toán tử to và khái niệm về các dãy (sequences). Hãy xây dựng một ứng dụng mẫu sử dụng các tính năng này để tạo ra một khung nhìn HTML tinh tế cho một tài liệu XML, và với các tính năng mới trong XSLT 2.0, hãy tạo các bản định kiểu ngắn hơn dễ duy trì hơn. Đồng thời, hãy dành một chút thời gian tìm hiểu về định kiểu dữ liệu trong XSLT 2.0, và học cách sử dụng phần tử mới <xsl:function>.

Một trong các khái niệm mới chủ yếu trong XPath 2.0 và XSLT 2.0 là tất cả mọi thứ là một dãy. Trong XPath 1.0 và XSLT 1.0, bạn thường làm việc với cây các nút. Tài liệu XML sau khi phân tích cú pháp là một cây có chứa nút document (tài liệu) và con cháu của nó. Nhờ sử dụng cây các nút, bạn có thể tìm thấy nút cho phần tử root (gốc), cùng với tất cả các con cháu, các thuộc tính, và các anh chị em của phần tử gốc đó. (Bất kỳ các nhận xét hoặc các lệnh xử lý nào bên ngoài phần tử gốc của tệp XML được coi là anh chị em của phần tử gốc này).


Khi bạn làm việc với một tài liệu XML trong XPath 2.0 và XSLT 2.0, bạn sử dụng dãy này theo cùng một cách như cấu trúc cây trong XPath 1.0 và XSLT 1.0. Dãy này chứa một mục duy nhất (nút tài liệu), và bạn sử dụng nó giống như cách bạn vẫn luôn áp dụng. Tuy nhiên, bạn có thể tạo ra các dãy các giá trị nguyên tử. Liệt kê 1, được lấy từ ứng dụng mẫu sắp tới, trong đó bạn sẽ quản lý dữ liệu cho một giải đấu đấu loại trực tiếp gồm 16 đội, cho thấy một ví dụ về một dãy các giá trị nguyên tử.
Liệt kê 1. Một dãy các giá trị nguyên tử
View Code:


<xsl:variable name="seeds" as="xs:integer*">
  <xsl:sequence
    select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>

Mã này định nghĩa biến $seeds. Phần tử mới <xsl:sequence> định nghĩa một dãy các mục, như bạn có thể đoán ra. Trong trường hợp này, các mục có các kiểu xs:integer của Lược đồ XML. Thuộc tính mới as định nghĩa kiểu dữ liệu của biến, dấu hoa thị (xs:integer*) có nghĩa là dãy này chứa từ không đến nhiều số nguyên. Trong XPath 1.0 và XSLT 1.0, bạn sẽ tạo 16 nút văn bản khác nhau và sau đó nhóm các nút đó lại thành một biến. Trong XPath 2.0 và XSLT 2.0, dãy hoạt động như là một mảng một chiều của các số, chính là cái mà bạn muốn cho ứng dụng mẫu.

Các dãy tuân theo một vài quy tắc. Đầu tiên, chúng không thể chứa các dãy khác. Nếu bạn tạo một dãy mới từ một dãy có ba mục, theo sau là một dãy khác có ba mục, thì kết quả sẽ là một dãy mới có sáu mục. Thứ hai, các dãy cho phép bạn pha trộn các nút và các mục. Bạn có thể tạo ra một dãy có chứa các giá trị nguyên tử được hiển thị trong Liệt kê 1, cộng với tất cả các phần tử <contestant>, như trong Liệt kê 2.

Liệt kê 2. Một dãy các nút và các giá trị nguyên tử
View Code:


<xsl:variable name="seeds" as="item()*">
  <xsl:for-each select="/bracket/contestants/contestant">
    <xsl:copy-of select="."/>
  </xsl:for-each>
  <xsl:sequence
    select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>


Biến $seeds chứa tất cả các nút contestant (đội tham gia thi đấu) và 16 giá trị nguyên tử mà bạn đã dùng trước đó. Lưu ý rằng kiểu dữ liệu của biến đó là item()*. Một item là một nút hoặc một giá trị nguyên tử, do đó, biến này có thể chứa bất cứ cái gì.

Bây giờ bạn đã biết những điều cơ bản về các dãy và các mục, chúng ta hãy xem xét toán tử to, là toán tử mới trong XPath 2.0 và XSLT 2.0. Nó cho phép bạn chọn một loạt các số nguyên. Ví dụ, bạn có thể tạo một dãy như trong Liệt kê 3.
Liệt kê 3. Một dãy các số nguyên được tạo bằng toán tử to
View Code:


<xsl:variable name="range" as="item()*">
  <xsl:sequence select="1 to 16"/>
</xsl:variable>


Mã này tạo ra một biến có tên là $range chứa các số nguyên từ 1 đến 16. Trong ứng dụng mẫu, bạn có thể sử dụng toán tử to làm một cơ chế vòng lặp, như trong Liệt kê 4.

Liệt kê 4. Sử dụng toán tử to để tạo vòng lặp
View Code:
<xsl:for-each select="1 to 32">
  <!-- Do something useful here -->
</xsl:for-each>


Trước khi bạn xây dựng bản định kiểu, hãy xem xét ứng dụng mẫu chi tiết hơn.

Hiểu ứng dụng mẫu

Trong ứng dụng mẫu cho bài viết này, bạn sẽ quản lý dữ liệu cho một giải đấu đấu loại trực tiếp gồm 16 đội. Như bạn đã mong đợi, dữ liệu giải đấu được thể hiện bằng XML. Bạn sẽ tạo một bản định kiểu XSLT 2.0 để biến đổi dữ liệu XML thành một bảng HTML biểu thị các kết quả của giải đấu. Liệt kê 5 cho thấy định dạng tài liệu XML.

Liệt kê 5. Tài liệu XML có dữ liệu giải đấu
View Code:
<?xml version="1.0" encoding="UTF-8"?>
<!-- tourney.xml -->
<bracket>
   <title>RSDC Smackdown</title>
   <contestants>
      <contestant seed="1" image="images/homerSimpson.png">Donuts</contestant>
      <contestant seed="2" image="images/caffeine.png">Caffeine</contestant>
      <contestant seed="3" image="images/fearlessFreep.png">Fearless Freep</contestant>
      <contestant seed="4" image="images/wmd.jpg">Weapons of Mass Destruction</contestant>
      <contestant seed="5" image="images/haroldPie.jpg">Pie</contestant>
      <contestant seed="6" image="images/adamAnt.png">Adam Ant</contestant>
      <contestant seed="7" image="images/georgeWBush.jpg">Misunderestimated</contestant>
      <contestant seed="8" image="images/sillyPutty.jpg">Silly Putty</contestant>
      <contestant seed="9" image="images/krazyGlue.jpg">Krazy Glue</contestant>
      <contestant seed="10" image="images/snoopDogg.png">Biz-Implification</contestant>
      <contestant seed="11" image="images/atomAnt.png">Atom Ant</contestant>
      <contestant seed="12" image="images/ajaxcan.png">AJAX</contestant>
      <contestant seed="13" image="images/darthVader.jpg">Darth Vader</contestant>
      <contestant seed="14" image="images/nastyCanasta.png">Nasty Canasta</contestant>
      <contestant seed="15" image="images/jcp.png">Java Community Process</contestant>
      <contestant seed="16" image="images/andre.png">Andre the Giant</contestant>
   </contestants>
   <results>
      <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
      <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
      <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
      <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
      <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
      <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
      <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
      <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
      <result round="2" firstSeed="1" secondSeed="9" winnerSeed="1"/>
      <result round="2" firstSeed="5" secondSeed="4" winnerSeed="5"/>
      <result round="2" firstSeed="11" secondSeed="3" winnerSeed="3"/>
      <result round="2" firstSeed="10" secondSeed="2" winnerSeed="2"/>
      <result round="3" firstSeed="1" secondSeed="5" winnerSeed="1"/>
      <result round="3" firstSeed="3" secondSeed="2" winnerSeed="2"/>
      <result round="4" firstSeed="1" secondSeed="2" winnerSeed="2"/>
   </results>
</bracket>     


Trong phần tử <title>, bạn có thể thấy tên của giải đấu là RSDC Smackdown. Tài liệu này biểu diễn các kết quả thực tế từ một phiên họp tại Hội nghị phát triển phần mềm Rational của IBM trong năm nay.

Một khung bảng chứa 16 phần tử <contestant> (đội tham gia thi đấu), mỗi phần tử có tên (văn bản của nó), số hạt giống, và hình ảnh. Một giải đấu 16 đội có 15 cặp đấu. Mỗi cặp đấu được biểu diễn bằng một phần tử <result>. Bốn dữ liệu gắn với mỗi cặp đấu là: vòng đấu của trận đấu ấy, (thuộc tính round), các số hạt giống của hai đội (được lưu trữ trong các thuộc tính firstSeed và secondSeed), và số hạt giống của đội thắng (thuộc tính winnerSeed). Nhiệm vụ của bạn là lấy tài liệu XML này và chuyển đổi nó thành một bảng HTML để minh họa các kết quả, như hiển thị trong Hình 1.

Hình 1. Các kết quả giải đấu trong một bảng HTML


Bảng này có 32 hàng và 5 cột. Khi sử dụng cách tiếp cận XSLT 1.0, bạn có thể xây dựng mỗi lần một hàng của bảng HTML, như trong Liệt kê 6.

Liệt kê 6. Xây dựng một hàng của bảng HTML mỗi lần
View Code:
<!-- Row 1 -->
<tr>
  <td style="border: none; border-top: solid; border-right: solid;">
    <xsl:text>[1] </xsl:text>
    <xsl:value-of select="$contestants[@seed='1']/>
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
  <td style="border: none;>
    &nbsp;
  </td>
</tr>
<!-- Row 2 -->
. . .


Cách này sẽ thực hiện được, nhưng bản định kiểu sẽ rất khó duy trì. Mã cho mỗi hàng và cột được lặp lại trong suốt bản định kiểu. Vấn đề chính ở đây là bạn cần 32 hàng trong bảng kết quả đầu ra. Mỗi một trong số 32 hàng chứa dữ liệu từ một phần tử trong tài liệu XML (hoặc một <contestant> hay một <result>). Thật không may, bạn không có 32 phần tử để có thể lặp duyệt qua chúng. Bạn có thể sử dụng <xsl:for-each select="contestants/contestant|results/result">, nhưng các phần tử đó không xuất hiện theo thứ tự mà bạn cần đến chúng trong bảng. XSLT 1.0 không có nhiều công cụ để giúp bạn làm việc này.

Bạn có thể cấu trúc lại mã để làm cho bản định kiểu đơn giản hơn nhiều. Có các mẫu hình cho kiểu dáng và nội dung của các ô, và bạn có thể sử dụng các tính năng mới của XPath 2.0 và XSLT 2.0 để lặp duyệt qua các mẫu hình đó. Bản định kiểu cuối cùng là nhỏ hơn khoảng 70% so với phiên bản ban đầu (phải thừa nhận là vụng về). Trước khi bạn xây dựng bản định kiểu đó, chúng ta hãy xem xét cách cấu trúc lại mã.
Cấu trúc lại mã - Tính toán các kiểu dáng ô của bảng

Để cấu trúc lại mã, hãy bắt đầu di chuyển thông tin kiểu dáng sang một bản định kiểu CSS. Như bạn có thể thấy trong Hình 2, bảng khung HTML có 5 kiểu dáng ô khác nhau: None, MatchupStart, MatchupMiddle, MatchupEnd, và Solid.

Hình 2. Các kiểu dáng đường viền ô trong bảng HTML

Liệt kê 7 cho thấy mã CSS trông như thế nào.
Liệt kê 7. Bản định kiểu CSS
View Code:

.None          { width: 20%; }
.MatchupStart  { width: 20%; 
                 border: none; border-top: solid; 
                 border-right: solid; }
.MatchupMiddle { width: 20%; 
                 border: none; border-right: solid; }
.MatchupEnd    { width: 20%; 
                 border: none; border-bottom: solid; 
                 border-right: solid; }
.Solid         { width: 20%; 
                 border: solid; }

Các kiểu dáng đường viền làm cho việc xem kết quả của giải đấu thành dễ dàng. Các đường ngang nối hai ô cho biết rằng hai đối thủ nào đã đối đầu với nhau; ô có đường viền liền nét trong cột tiếp sau cho thấy người chiến thắng của cặp đấu này. Nhìn vào các kiểu dáng đường viền, bạn có thể thấy một mẫu hình xác định: Đối với mỗi cặp đối thủ, các ô trước đối thủ đầu tiên không có đường viền (kiểu dáng là None), các ô dành cho hai đối thủ có đường viền liền nét (kiểu dáng Solid), các ô giữa hai đối thủ chỉ có đường viền ở bên phải (kiểu dáng MatchupMiddle), và các ô sau đối thủ cuối cùng không có đường viền (kiểu dáng None). Điều này hơi khác một chút so với cột đầu tiên. Vì các cặp đối thủ trong cột đầu tiên là quá gần nhau, nên ô dành cho đối thủ đầu tiên có một đường viền trên đỉnh và bên phải (kiểu dáng MatchupStart), và ô dành cho đối thủ thứ hai có một đường viền dưới đáy và bên phải (kiểu dáng MatchupEnd).
Lưu ý rằng các cặp đối thủ cách nhau xa hơn trong mỗi cột. Khoảng cách một ô giữa các đối thủ cột 1, khoảng cách ba ô ở cột 2, khoảng cách bảy ô ở cột 3, và khoảng cách 15 ô ở cột 4. Kích thước của mỗi khoảng cách bằng một lũy thừa hai trừ một, do đó, có một mẫu hình xác định ở đây.
Mẫu hình tổng quát xuyên suốt bảng này là mỗi cột có chứa một nhóm các ô lặp lại. Kích thước của các nhóm lặp lại (mà tôi sẽ gọi là chu kỳ của cột, vì thiếu một thuật ngữ hay hơn) là 4, 8, 16, 32 và 64 trong năm cột. Mỗi nhóm lặp lại có hai đối thủ, các ô giữa hai đối thủ và các ô trước và sau hai đối thủ.
Trong mỗi cột, hãy sử dụng hai giá trị trong tính toán của bạn: $period, là kích thước của nhóm lặp lại, và $oneQuarter, bằng 1/4 kích thước của chu kỳ. (Việc lưu $period div 4 vào một biến làm cho mã sạch hơn). Bảng 1 cho thấy các quy tắc tổng quát hóa cho các kiểu dáng đường viền của ô.

Bảng 1. Các kiểu dáng đường viền của ô trong bảng HTML
Công thứcCột 1(chu kỳ 4)Cột 2 (chu kỳ 8)Cột 3 (chu kỳ 16)Cột 4 (chu kỳ 32)Cột 5 (chu kỳ 64)
$row < $oneQuarter hoặc $row > $period - $oneQuarter N/AHàng 1, kiểu dáng NoneCác hàng 1-3, kiểu dáng NoneCác hàng 1-7, kiểu dáng NoneCác hàng 1-15, kiểu dáng None
$row = $oneQuarter Hàng 1, kiểu dáng MatchupStartHàng 2, kiểu dáng SolidHàng 4, kiểu dáng SolidHàng 8, kiểu dáng SolidHàng 16, kiểu dáng Solid
$row > $oneQuarter $row < $period - $oneQuarter Hàng 2, kiểu dáng MatchupMiddleCác hàng 3-5, kiểu dáng MatchupMiddleCác hàng 5-11, kiểu dáng MatchupMiddleCác hàng 9-23, kiểu dáng MatchupMiddleCác hàng 17-32, kiểu dáng None
$row = $period - $oneQuarter Hàng 3, kiểu dáng MatchupEndHàng 6, kiểu dáng SolidHàng 12, kiểu dáng SolidHàng 24, kiểu dáng SolidN/A
$row < $oneQuarter hoặc $row > $period - $oneQuarter Hàng 4, kiểu dáng NoneCác hàng 7-8, kiểu dáng NoneCác hàng 13-16, kiểu dáng NoneCác hàng 25-32, kiểu dáng NoneN/A
Bây giờ bạn đã có một công thức đẹp đẽ để tìm ra kiểu dáng của bất kỳ ô cụ thể nào trong bảng. Cho trước số cột và số hàng, công thức làm việc như một phép màu.
 Cấu trúc lại mã - Tính toán nội dung ô trong bảng

Khi bạn tạo ra một ô trong bảng, tất nhiên, bạn cũng cần phải đưa nội dung thích hợp vào trong ô đó. Việc này cũng có một mẫu hình thú vị. Bất cứ ô nào có kiểu dáng là None hoặc MatchupMiddle đều không có bất kỳ nội dung nào. Điều đó có nghĩa là bạn chỉ phải lo nghĩ về việc tìm kiếm các nội dung phù hợp cho các ô có ba kiểu dáng khác còn lại.

Trong Bảng 1, bạn có thể thấy rằng các ô có nội dung có thuộc tính $row = $oneQuarter hoặc $row = $period - $oneQuarter. Với cột đầu tiên, bạn chỉ cần viết số hạt giống và tên của đội thi đấu thích hợp. Các cặp đấu đều dựa vào các cách chọn hạt giống và mô tả các cặp đấu được thể hiện trong Bảng 2.

Bảng 2. Các cặp đấu dựa vào cách chọn hạt giống
[1] versus [16]
[8] versus [9]
[5] versus [12]
[4] versus [13]
[6] versus [11]
[3] versus [14]
[7] versus [10]
[2] versus [15]

Các cặp đấu được bố trí sao cho nếu hạt giống xếp cao hơn luôn thắng, thì đội xếp hạt giống cao nhất sẽ luôn đấu với đội xếp hạt giống thấp nhất còn lại, đội xếp hạt giống thứ hai sẽ luôn đấu với đội xếp thứ hai từ dưới lên còn lại, và v.v.. Nhìn vào các hạt giống theo thứ tự này (1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15), bạn có thể thấy chúng phù hợp với các giá trị trong $seeds. Các đối thủ được hiển thị trong cột 1 theo thứ tự này.

Đối thủ xuất hiện trong tất cả các hàng khác, có nghĩa là hàng 1 có hạt giống đầu tiên trong dãy, hàng 3 có hạt giống thứ hai trong dãy, hàng 5 hạt giống thứ ba trong dãy, và v.v.. Ở đây mẫu hình là $row + 1 div 2 = $index. Nói cách khác, lấy số hàng, cộng 1, và chia cho 2 để nhận được chỉ số của hạt giống trong dãy $seeds. Nội dung ô của bảng là số hạt giống tiếp theo là tên của đối thủ có số hạt giống đó.

Mẫu hình trên bảo đảm nội dung cho cột 1. Với các cột từ 2 đến 5, như bạn có thể dự kiến, sẽ phức tạp hơn. Thay vì xem xét các phần tử <contestant>, bạn cần phải xem xét các kết quả được hiển thị trong 15 phần tử <result>.

Cột 2 chứa những đội thắng ở vòng 1. Điều đó có nghĩa là bạn cần phải xem xét các phần tử <result> có thuộc tính round="1". Để giữ cho mọi thứ đơn giản hơn, đội thắng của cặp đấu đầu tiên (hạt giống 1 đấu với 16) được lưu trữ trong phần tử <result> đầu tiên, đội thắng của cặp đấu thứ hai (hạt giống 8 đấu với 9) được lưu trữ trong phần tử <result> thứ hai , và v.v.. Những đội thắng trong cột 2 được hiển thị trong các hàng 2, 6, 10, 14, 18, 22, 26, và 30. Hãy tìm kiếm một mẫu hình, hàng 2 sử dụng phần tử <result> đầu tiên, hàng 6 sử dụng phần tử <result> thứ hai, và hàng 10 sử dụng phần tử <result> thứ ba. Với cột này, nếu bạn lấy số hàng, cộng 2, và chia cho 4, bạn sẽ nhận được vị trí của phần tử <result round="1"> phù hợp.

Cột 3 và 4 được xử lý tương tự. Những đội thắng trong cột 3 được hiển thị trong các hàng 4, 12, 20, và 28 (mẫu hình ở đây là cộng 4 và chia cho 8). Những đội thắng trong cột 4 được hiển thị trong hàng 8 và 24 (cộng 8 và chia cho 16). Cột 5 hiển thị một đội duy nhất — đội thắng của toàn bộ giải đấu. Nếu bạn đang ở hàng 16, bạn sẽ hiển thị đội thắng từ phần tử <result round="4">.

Cách cấu trúc lại để tìm vị trí của giá trị mà bạn tìm kiếm là ($row + $oneQuarter) div ($oneQuarter * 2). Việc tăng dần kích thước của mẫu hình lặp lại làm cho mã đơn giản. Với cột 1, vị trí tính được là chỉ số trong dãy các hạt giống; với các cột khác, vị trí tính được là vị trí của phần tử <result>.

Bây giờ bạn có một công thức đẹp đẽ để xác định cả nội dung lẫn kiểu dáng của mỗi ô trong bảng. Dựa vào số hàng và cột, bạn có thể tìm ra tất cả mọi thứ bạn cần biết.
Khai thác sức mạnh của XPath 2.0 và XSLT 2.0

Bây giờ bạn có thể sử dụng các kỹ thuật mới trong XPath 2.0 và XSLT 2.0 để trau chuốt bản định kiểu. Để bắt đầu, hãy sử dụng toán tử to. Nếu bạn dựng bảng bằng một ngôn ngữ lập trình thủ tục, bạn có thể làm điều gì đó tương tự như Liệt kê 8.

Liệt kê 8. Cách tiếp cận thủ tục cho bảng định kiểu
View Code:

      for (int row=1; row<=32; row++)
        for (int column=1; column<=5; column++)
          // Build each cell in the table here


Với XPath 2.0 và XSLT 2.0, bạn sử dụng <xsl:for-each> để thay thế các vòng lặp for mà bạn sử dụng trong một ngôn ngữ thủ tục. Liệt kê 9 cho bạn biết cách làm như thế nào.

Liệt kê 9. Thực hiện vòng lặp for với toán tử to
View Code:

<xsl:for-each select="1 to 32">
  <xsl:variable name="outerIndex" select="."/>
    <tr>
      <xsl:for-each select="1 to 5">


Bạn cần phải giải quyết một vài tình tiết phức tạp ở đây. Đầu tiên, trong XPath 1.0 và XSLT 1.0, việc sử dụng <xsl:for-each> đã làm thay đổi ngữ cảnh cho mỗi lần lặp. Ví dụ, nếu bạn sử dụng <xsl:for-each select="contestants/contestant>, thì nút ngữ cảnh là <contestant> mới nhất trong mỗi lần lặp. Khi bạn sử dụng toán tử to để lặp duyệt qua các số nguyên khác nhau, mục ngữ cảnh (đó là ngữ cảnh item trong 2.0) là không xác định. Như bạn có thể thấy trong Liệt kê 9, bạn cần phải ghi lưu giá trị hiện tại của <xsl:for-each> bên ngoài bởi vì nó không có sẵn cho bạn ở <xsl:for-each> bên trong.

Nhưng còn tệ hơn nữa. Nếu mục ngữ cảnh là không xác định, bạn không có cách nào để chọn các nút từ tài liệu. Nếu bạn biết bạn đang ở hàng 1, cột 1, bạn có thể nhận được mục đầu tiên từ dãy $seeds vì $seeds là một biến chung. Điều đó nói cho bạn biết rằng bạn cần phải tìm phần tử <contestant seed="1">. Thật không may, bạn không thể nhận được bất cứ điều gì trong tài liệu. Ngay cả việc sử dụng một biểu thức XPath tuyệt đối như là /bracket/contestants/contestant[@seed='1'] cũng không thành công. Vì lý do đó, bạn cần lưu trữ các nút mà bạn quan tâm làm các một biến chung. Liệt kê 10 cho bạn thấy cách truy cập các một biến chung, bất cứ khi nào và ở bất cứ nơi nào bạn cần.

Liệt kê 10. Một biến chung lưu trữ các nút bạn cần
View Code:

<xsl:variable name="results" select="/bracket/results"/>

<xsl:variable name="contestants" select="/bracket/contestants"/>


Nếu bạn cần lấy tên của đội đấu hạt giống 16 , biểu thức XPath là $contestants/contestant[@seed="16"]. Bạn truy cập phần tử <result> tương tự; nếu bạn cần nhận được đội thắng trong cặp đấu thứ hai trong vòng 2, biểu thức là $results/result[@round="2"][2]/@winnerSeed. Liệt kê 11 cho thấy hai một biến chung nữa mà bạn có thể sử dụng để tạo ra bảng HTML.

Liệt kê 11. Các một biến chung có ích khác
View Code:

<xsl:variable name="periods" as="xs:integer*">
  <xsl:sequence select="(4, 8, 16, 32, 64)"/>
</xsl:variable>

<xsl:variable name="backgroundColors" as="xs:string*">
  <xsl:sequence
    select="('background: #CCCCCC;', 'background: #9999CC;',
             'background: #99CCCC;', 'background: #CC99CC;',
             'background: #CCCC99;')"/>
</xsl:variable>


Các biến này lưu trữ giá trị của chu kỳ cho mỗi cột và màu nền cho mỗi cột. Để sử dụng từng biến, bạn chỉ cần sử dụng số cột làm chỉ số của giá trị duy nhất mà bạn muốn.

Một cải tiến thú vị trong XPath 2.0 và XSLT 2.0 là phần tử <xsl:function>. Hãy tạo hai hàm trong bản định kiểu là: cellStyle và getResults. Hàm đầu tiên trả về kiểu dáng đường viền cho mỗi ô, trong khi hàm thứ hai trả về kết quả (nếu có) cho một cặp đấu. Các tham số cho cả hai hàm là số hàng và cột của ô. Liệt kê 12 trình bày mã cho hàm cellStyle.

Liệt kê 12. Hàm cellStyle
View Code:

<xsl:function name="bracket:cellStyle" as="xs:string">
  <xsl:param name="row" as="xs:integer"/>
  <xsl:param name="column" as="xs:integer"/>
 
  <xsl:variable name="period" as="xs:integer"
    select="subsequence($periods, $column, 1)"/>
  <xsl:variable name="oneQuarter" as="xs:integer"
    select="$period div 4"/>
  <xsl:variable name="lastColumn" as="xs:boolean"
    select="$oneQuarter = count($contestants/contestant)"/>
  <xsl:variable name="position" select="$row mod $period"/>
 
  <xsl:value-of
    select="if ($position = $oneQuarter) then
                (if ($column = 1) then 'MatchupStart'
                    else 'Solid')
            else if ($position = $period - $oneQuarter) then
                     (if ($column = 1) then 'MatchupEnd'
                         else 'Solid')
            else if ($lastColumn) then 'None'
            else if ($position &lt; $oneQuarter or
                     $position &gt; $period - $oneQuarter) then 'None'
            else 'MatchupMiddle'"/>
</xsl:function>


Trước khi bạn xác định kiểu dáng ô, bạn sử dụng 4 biến. Bạn lấy ra giá trị $period từ một biến chung $periods. Như tôi đã đề cập ở trên, $oneQuarter đơn giản là $period div 4. Biến $lastColumn kiểu boolean là kết quả so sánh $oneQuarter với tổng số đội trong giải đấu. Nếu các giá trị đó bằng nhau, bạn đang xử lý cột cuối cùng. Cuối cùng, biến $position cho biết vị trí của hàng hiện tại trong mẫu hình. Nói cách khác, hàng 9 trong bảng là hàng đầu tiên của nhóm lặp lại cho các cột 1 và 2. Hãy sử dụng vị trí của hàng trong mẫu hình để tìm ra kiểu dáng của ô.

XPath 2.0 và XSLT 2.0 có một toán tử if, gồm một biểu thức (trong ngoặc), tiếp sau là mệnh đề then và mệnh đề else. Toàn bộ các thành phần này nằm trong thuộc tính select của <xsl:value-of>. Trong ví dụ này, bạn thay thế phần tử <xsl:choose> dài dòng hơn nhiều bằng chỉ một biểu thức. Mã ở đây là khá đơn giản, dựa vào cuộc thảo luận về các công thức bảng; nếu logic phức tạp hơn, thì có lẽ sử dụng <xsl:choose>, sẽ dễ bảo trì hơn, mặc dù nó đòi hỏi phải gõ bàn phím nhiều hơn.

Một số lưu ý về cú pháp cho hàm <xsl:function>: Đầu tiên, bạn cần phải khai báo một vùng tên mới cho các hàm này. Nếu bạn gọi bracket:cellStyle() trong một biểu thức XPath, vùng tên này sẽ cho bộ xử lý XSLT 2.0 biết cách tìm hàm này. Thứ hai, lưu ý rằng bạn đã sử dụng thuộc tính as="xs:string" để chỉ ra rằng hàm này trả về một chuỗi. Phần tử <xsl:value-of> trả về một trong năm tên kiểu dáng, đó là đầu ra của hàm.

Hàm getResults có phức tạp hơn một chút, nhưng không nhiều, bạn có thể thấy trong Liệt kê 13.

Liệt kê 13. Hàm getResults
View Code:

<xsl:function name="bracket:getResults" as="xs:string">
  <xsl:param name="row" as="xs:integer"/>
  <xsl:param name="column" as="xs:integer"/>
 
  <xsl:variable name="period" as="xs:integer"
    select="subsequence($periods, $column, 1)"/>
  <xsl:variable name="oneQuarter" as="xs:integer"
    select="$period div 4"/>
  <xsl:variable name="position" select="$row mod $period"/>
 
  <xsl:choose>
    <xsl:when test="$position = $oneQuarter
                    or
                    $position = $period - $oneQuarter">
      <xsl:variable name="round" select="$column - 1"/>
      <xsl:variable name="index"
        select="($row + $oneQuarter) div ($oneQuarter * 2)"/>
      <xsl:variable name="currentSeed"
        select="if ($column = 1) then
                  subsequence($seeds, $index, 1)
                else
                  $results/result[@round=$round][$index]/@winnerSeed"/>
      <xsl:choose>
        <xsl:when test="string-length(string($currentSeed))">
          <xsl:value-of
            select="concat('[', $currentSeed, '] ',
                    $contestants/contestant[@seed=$currentSeed])"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:text>&#160;</xsl:text>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text>&#160;</xsl:text>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>


Bạn có các tham số tương tự như trước đây. Bạn phải làm một số việc xử lý đặc biệt ở đây cho cột 1, có chứa dữ liệu từ các phần tử <contestant>, trong khi các cột khác chứa dữ liệu từ các phần tử <result>. Cũng giống như với hàm cellStyle bạn tính toán $period và $position, và bạn sử dụng biến $oneQuarter để đơn giản hóa các công thức.

Nếu ô chứa một đội thi đấu, bạn tính toán thêm ba biến. Biến $round là số cột trừ đi một (cột 2 chứa kết quả của vòng 1), và bạn tính toán biến $index theo công thức thảo luận ở trên.

Bạn cần phải làm theo hai bước để tìm dữ liệu thích hợp. Đầu tiên, thiết lập giá trị của biến $currentSeed. Nếu đây là vòng 1, bạn sử dụng hàm subsequence mới để chọn một giá trị từ biến $seeds. Đối với các vòng khác, bạn lấy thuộc tính winnerSeed từ phần tử <result> thích hợp.

Thứ hai, ngoài việc xử lý cột 1 khác đi, bạn cần tính đến khả năng có một phần tử <result> không có đội thắng (winnerSeed=""). Nếu điều đó xảy ra, hãy trả về một khoảng trống cứng (&#160;). Theo cách XSLT 2.0 xử lý các kiểu dữ liệu (đây là một chủ đề cho một bài viết sắp tới), bạn cần phải chuyển đổi $currentSeed thành một chuỗi, rồi kiểm tra độ dài của chuỗi. Nếu chiều dài của chuỗi bằng không, hãy trả về một khoảng trống cứng; ngược lại, hãy trả về hạt giống và tên của đội thi đấu.

Cuối cùng, lưu ý rằng bạn sử dụng hàm <xsl:choose> ở đây. Mặc dù bạn có thể làm mọi thứ với chỉ một câu lệnh if của XPath, mã sẽ rất cồng kềnh. Mặc dù hàm <xsl:choose> dài dòng hơn, mã sạch hơn và dễ hiểu hơn.

Bây giờ khi bạn đã thiết lập các một biến chung và các hàm mà bạn cần, trọng tâm của bản kiểu định kiểu là đơn giản và đẹp đẽ, bạn có thể thấy trong Liệt kê 14.

Liệt kê 14. Trọng tâm của bản định kiểu
View Code:

<xsl:for-each select="1 to 32">
  <xsl:variable name="outerIndex" select="."/>
  <tr>
    <xsl:for-each select="1 to 5">
      <td style="{subsequence($backgroundColors, ., 1)}"
          class="{bracket:cellStyle($outerIndex, .)}">
        <xsl:value-of
          select="bracket:getResults($outerIndex, .)"/>
      </td>
    </xsl:for-each>
  </tr>
</xsl:for-each>


Bạn có hai vòng lặp tạo ra 5 cột với 32 hàng của bảng. Đối với mỗi ô, màu nền được dựa vào số cột hiện tại. Hàm cellStyle xác định kiểu dáng đường viền (class="x"), và hàm getResults xác định giá trị của ô. 
Sử dụng bản định kiểu

Như bạn đã có thể dự kiến, cần một trình xử lý XSLT 2.0 để sử dụng bản định kiểu này. Tôi sẽ không in lại toàn bộ bản định kiểu ở đây, nhưng bạn phải sử dụng <xsl:stylesheet version="2.0"> để cho trình xử lý sẽ chạy ở chế độ XSLT 2.0. Tôi rất khuyến cáo nên dùng trình xử lý Saxon của Michael Kay (xem phần Tài nguyên để biết thêm chi tiết). Tiến sĩ Kay vốn là người soạn thảo đặc tả XSLT 2.0, và Saxon, theo một vài phương diện, đã là một ca kiểm thử khi đặc tả này đang được phát triển. Nếu bạn sử dụng Saxon, hãy dùng lệnh dưới đây để biến đổi tệp XML tourney.xml bằng cách sử dụng bản định kiểu results-html.xsl và viết đầu ra cho tệp results.html:

java net.sf.saxon.Transform -o results.html tourney.xml results-html.xsl

Để minh họa cho tính linh hoạt của bản định kiểu này, hãy lấy ra một vài kết quả trong tệp XML và chạy phép biến đổi này. Liệt kê 15 cho thấy phần tử <result> trông như thế nào.

Liệt kê 15. Tài liệu XML với dữ liệu giải đấu chưa hoàn thành
View Code:

<?xml version="1.0" encoding="UTF-8"?>
<!-- incomplete-tourney.xml -->
<bracket>
. . .
   <results>
      <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
      <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
      <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
      <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
      <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
      <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
      <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
      <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
      <result round="4" firstSeed="" secondSeed="" winnerSeed=""/>
   </results>
</bracket>     


Liệt kê 15 biểu diễn trạng thái của giải đấu sau vòng đầu tiên. Tất cả các phần tử <result> cho vòng 2, 3, và 4 có một thuộc tính trống winnerSeed. Mặc dù vậy, bản định kiểu tạo ra khung bảng chính xác, bạn có thể thấy trong Hình 3.

Hình 3. Khung bảng cho một giải đấu chưa hoàn thành

Tóm tắt
Bài viết này đã trình diễn nhiều tính năng mới của XPath 2.0 và XSLT 2.0. Trong ứng dụng mẫu, bạn đã chọn một bản định kiểu cồng kềnh và đã cấu trúc lại nó thành một đoạn mã nhỏ hơn nhiều và dễ bào trì hơn. Một phần nỗ lực của bạn là để phân tích mã nhằm tìm ra các mẫu hình, nhưng nếu không có toán tử to, hàm <xsl:function>, và dãy các mục, thì việc sắp xếp hợp lý bảng định kiểu nhiều như bạn đã làm sẽ rất khó khăn.
Đáng kể nhất, bạn đã tạo ra các hàm chung có thể xử lý bất kỳ số lượng đối thủ nào. Ví dụ, để thay đổi bản định kiểu nhằm xử lý một giải đấu có 32 đội, bạn cần phải thay đổi các dãy $seeds$periods. Bạn cũng cần thay thế select="1 to 5" bằng select="1 to $rounds", ở đây $rounds là số vòng thi đấu trong giải đấu. Tất nhiên, giải pháp đẹp đẽ nhất là tạo ra hàm XSLT 2.0 tính toán các giá trị cho bất kỳ khung bảng chung nào, bao gồm cả số vòng đấu, dãy các hạt giống (một khung bảng 32-đội mô tả các cặp đấu giữa 1 và 32, 2 và 31, và v.v..), và các chu kỳ cho các cột khác nhau. Thách thức này được để dành làm một bài tập cho người đọc.
Cốt lõi của vấn đề là bạn cần lặp duyệt qua 32 hàng của bảng, nhưng XSLT 1.0 không cung cấp cho bạn cách nào thiết thực nào để làm điều đó. Các tính năng mới của XPath 2.0 và XSLT 2.0 giúp bạn giải quyết vấn đề này.

Tải bải giài tại đây: http://www.mediafire.com/?p9pklpgaht0fl59
Bookmark and Share

0 comments:

Post a Comment

Next previous home

Cộng đồng yêu thiết kế Việt Nam Thiet ke website, danang