Giới thiệu XSLT

Chuyển đổi định dạng dữ liệu XML bằng ngôn ngữ định dạng chuyển đổi mở rộng (Extensible Stylesheet Language Transformations - XSLT)

Nhu cầu chuyển đổi XML là rất cần thiết đến mức ngôn ngữ định dạng chuyển đổi mở rộng (Extensible Stylesheet Language Transformations - XSLT) được coi là một trong những đặc tính cơ bản của XML. Bài hướng dẫn này giải thích làm thế nào để tạo các bảng định kiểu XSLT (XSLT stylesheets). Bài này cũng đề cập đến Xpath (cho phép bạn chọn một phần cụ thể trong tài liệu XML). Cuối cùng, bài viết này sẽ giúp bạn có một cái nhìn về những khả năng tiên tiến của XSLT.

Nicholas Chase, Tác giả tự do, Site Dynamics Interactive Communications

Nicholas Chase đã phát triển trang web cho các công ty lớn như Lucent Technologies, Sun Microsystems, Oracle, và Tampa Bay Buccaneers. Nick đã từng là một giáo viên vật lý ở trường phổ thông, một nhà quản lý thiết bị phóng xạ mức thấp, một nhà biên tập tạp chí khoa học viễn tưởng trực tuyến, một kỹ sư đa phương tiện, một hướng dẫn của Oracle, và một trưởng phòng công nghệ của một công ty tương tác truyền thông. Nick là tác giả của một số sách



20 05 2009

Trước khi bắt đầu

Bài viết này dành cho các nhà phát triển muốn sử dụng ngôn ngữ định kiểu mở rộng chuyển đổi (Extensible Stylesheet Language Transformations - XSLT) để chuyển đổi dữ liệu XML sang một kiểu khác mà không cần kỹ năng lập trình Java™ hoặc ngôn ngữ khác.

Bài viết này giả định rằng bạn đã quen thuộc với XML, nhưng chưa làm việc với bất kỳ khía cạnh riêng biệt nào của ngôn ngữ, do đó bạn chỉ cần có những hiểu biết cơ bản là đủ. Bạn sẽ không cần phải có bất cứ ngôn ngữ lập trình cụ thể nào. Thậm chí, các khái niệm là hoàn toàn dễ hiểu và áp dụng vào bất cứ ngôn ngữ lập trình nào mà được hỗ trợ bởi trình xử lý nào.

Mục đích của bài viết này?

Bài viết này giới thiệu về ngôn ngữ định kiểu mở rộng chuyển đổi (XSLT). XSLT là một trong những nền tảng cơ sở của các đặc tính kỹ thuật liên quan đến XML, cho phép bạn chuyển đổi kiểu dữ liệu XML một cách dễ dàng.

Trong bài này, bạn sẽ học:

  • Các khái niệm cơ bản của XSLT
  • Sử dụng các mẫu đơn giản
  • truyền dữ liệu
  • Điều khiển các khoảng trống
  • Các khái niệm cơ bản của XPath
  • Các hàm của XPath
  • Phát biểu lặp và điều kiện
  • Nhập và bao hàm các bảng định kiểu (stylesheet) khác
  • Mở rộng XSLT
  • Các biến XSLT

Bắt đầu

Giả sử rằng bạn có một tập dữ liệu dưới dạng XML, bạn có thể tiếp tục lưu trữ dữ liệu đó dưới dạng XML vì nó có thể được sử dụng trên tất cả các nền tảng và với tất cả các ngôn ngữ. Tuy nhiên, một lúc nào đó bạn muốn chuyển cấu trúc XML của dữ liệu đó sang một định dạng khác để lưu trữ trong một hệ thống khác hoặc đơn giản là để hiển thị nó khác đi hoặc với mục đích khác.

Ví dụ, bạn có dữ liệu dạng XML và muốn hiển thị nó trên nền Web thì bạn phải chuyển nó sang dạng HTML. Bạn có thể từng bước thực hiện việc chuyển đổi từ XML sang HTML hoặc bạn đọc nó vào một DOM và chuyển đổi nó.

Nhưng thực tế là, bạn không cần phải làm như vậy. Có những cánh đơn giản hơn nhiều.

XSLT là gì?

Ngôn ngữ định kiểu mở rộng chuyển đổi (Extensible Stylesheet Language Transformations - XSLT) cung cấp cách chuyển đổi dữ liệu XML từ dạng này sang dạng khác một các tự động. Định dạng muốn chuyển sang thường có thể là là một tài liệu XML hoặc không; bạn có thể chuyển đổi dữ liệu XML sang bất cứ dạng gì bằng cách tạo ra các bảng định kiểu XSLT và thực hiện chuyển đổi dữ liệu. Nếu bạn muốn thay đổi định dạng đích, bạn chỉ việc thay đổi bảng định kiểu XSLT và thực hiện việc chuyển đổi lần nữa. Điều này rất hiệu cho những người không phải là lập trình viên, ví dụ như nhà thiết kế, họ có thể thay đổi XSLT để có được kết quả như ý.

Hãy xem một ví dụ.

Những việc bạn sẽ hoàn thành

Trong bài viết này, bạn sẽ chuyển đổi một tài liệu XML sang tài liệu XHTML để hiện thị trên một trang web. Dữ liệu đầu vào là một công thức chế biến thực phẩm dưới dạng XML (xem Ví dụ 1).

Ví dụ 1. Dữ liệu đầu vào
<recipes>
   <recipe>
       <name>Gush'gosh</name>
       <ingredients>
          
<ingredient><qty>1</qty><unit>pound</unit>
<food>hamburger</food></ingredient>
          
<ingredient><qty>1</qty><unit>pound</unit>
<food>elbow macaroni</food></ingredient>
          
<ingredient><qty>2</qty><unit>cups</unit>
<food>brown sugar</food></ingredient>
          <ingredient><qty>1</qty><unit>bag</unit>
<food>chopped onions</food></ingredient>
          
<ingredient><qty>1</qty><unit>teaspoon</unit>
<food>dried dill</food></ingredient>
       </ingredients>
       <instructions>
          <instruction>Brown the hamburger.</instruction>
          <instruction>Add onions and cook until
 transparent.</instruction>
          <instruction>Add brown sugar and dill.</instruction>
          <instruction>Cook and drain pasta.</instruction>
          <instruction>Combine meat and pasta.</instruction>
       </instructions>
   </recipe>
   
      <recipe>
       <name>A balanced breakfast</name>
       <ingredients>
          <ingredient><qty>1</qty><unit>cup</unit>
<food>cereal</food></ingredient>
          
<ingredient><qty>1</qty><unit>glass</unit>
<food>orange juice</food></ingredient>
          
<ingredient><qty>1</qty><unit>cup</unit>
<food>milk</food></ingredient>
          
<ingredient><qty>2</qty><unit>slices</unit>
<food>toast</food></ingredient>
       </ingredients>
       <instructions>
          <instruction>Combine cereal and milk in 
bowl.</instruction>
          <instruction>Add all ingredients to table.</instruction>
       </instructions>
   </recipe>

</recipes>

Ghi chú của biên tập viên: Các công thức này chỉ là ví dụ của tác giả. Công thức đúng cho Gush'gosh (được cung cấp bởi vợ anh ta) bao gồm 1 pound hamberger, 1 pound elbow macaroni, 1/2 chén đường nâu, 1 túi nhỏ hành cắt lát, 1 thìa cafe dried dill và 1 lon nhỏ nước sốt cà chua.

Tất nhiên, đây là một ví dụ đơn giản nên bạn không cần quan tâm đến chi tiết dữ liệu, nhưng dữ liệu XML thực sự của bạn có thể là mọi thứ từ nhật ký hoạt động cho đến thông tin tài chính.

Mục đích của chúng ta là chuyển đổi dữ liệu này sang trang XHTML để hiện thị các công thức một cách riêng biệt và định dạng nguyên liệu và chỉ dẫn của chúng (xem Ví dụ 2).

Ví dụ 2. Trang kết quả
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/TR/xhtml1/strict">
<head><title>Recipe</title></head>
<body>
<h2>Gush'gosh</h2>
<h3>Ingredients:</h3>
<p>       1 pound hamburger<br/>
          1 pound elbow macaroni<br/>
          2 cups brown sugar<br/>
          1 bag chopped onions<br/>
          1 teaspoon dried dill<br/>
</p>
<h3>Directions:</h3>
<ol>
          <li>Brown the hamburger.</li>
          <li>Add onions and cook until transparent.</li>
          <li>Add brown sugar and dill.</li>
          <li>Cook and drain pasta.</li>
          <li>Combine meat and pasta.</li>
</ol>

<h2>A balanced breakfast</h2>
<h3>Ingredients:</h3>
<p>
          1 cup cereal<br/>
          1 glass orange juice<br/>
          1 cup milk<br/>
          2 slices toast<br/>
</p>
<h3>Directions:</h3>
<ol>
          <li>Combine cereal and milk in bowl.</li>
          <li>Add all ingredients to table.</li>
</ol>
</body>
</html>

Bạn có thể hiển thị kết quả này trên trình duyệt như trong Hình 1.

Hình 1. Trang kết quả hiển thị trong trình duyệt
Trang kết quả hiển thị trong trình duyệt

Như đã nói ở trên, kết quả cuối cùng có thể là bất cứ định dạng gì như XML chẳng hạn.

Hãy bắt đầu bằng một chuyển đổi đơn giản.

Bảng định kiểu stylesheet cơ bản

Bản đinh kiểu stylesheet cơ bản nhất là một tài liệu XML bao gồm luôn kết quả của XSLT (xem Ví dụ 3).

Ví dụ 3. Bảng định kiểu stylesheet đơn giản nhất
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns="http://www.w3.org/TR/xhtml1/strict">
  <head>
    <title>Recipe</title>
  </head>
  <body>
    <h2><xsl:value-of select="/recipes/recipe/name"/></h2> 
    <h3>Ingredients:</h3>
    <p><xsl:value-of select="/recipes/recipe/ingredients"/></p>
    <h3>Directions:</h3>
    <p><xsl:value-of select="/recipes/recipe/instructions"/></p>
  </body>
</html>

Lưu ý cách dùng của không gian namespace xsl:. Thêm không gian namespace này để bộ xử lý biết được thành phần nào sẽ được xử lý, thành phần nào sẽ là kết quả. Phần value-of của các thành phần giúp cho bộ xử lý chèn các phần dữ liệu cụ thể vào vị đó. Và những phần dữ liệu chèn vào đó được quyết định bởi nội dung của thuộc tính select.

Thuộc tính select bao gồm các biểu diễn của XPath. XPath sẽ được thảo luận chi tiết trong phần nâng cao về XPath, nhưng ở đây bạn có thể nhìn thấy tên, ingredients, và các thành phần của chỉ lệnh được truy cập từng bước qua cấu trúc phân cấp của tài liệu. Nó bắt đầu từ thành phần gốc, /recipes, và tiếp tục xuống phía dưới.

Cách thực hiện chuyển đổi

Cách chuyển đổi đơn giản nhất là thêm các chỉ dẫn định kiểu xml-stylesheet vào tài liệu XML và hiển thị nó trong trình duyệt (xem Ví dụ 4).

Ví dụ 4. Thêm chỉ dẫn xử lý định kiểu xml-stylesheet vào tài liệu XML
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="basicstylesheet.xsl" version="1.0" ?>
<recipes>
   <recipe>
       <name>Gush'gosh</name>
...

Chỉ dẫn xử lý này yêu cầu trình duyệt lấy bảng định kiểu stylesheet từ basicstylesheet.xsl và sử dụng nó để chuyển đổi dữ liệu XML và xuất kết quả ra. Nếu bạn mở tài liệu XML đó trong Microsoft® Internet Explorer®, bạn sẽ thấy kết quả như trong Hình 2.

Hình 2. Lấy bảng định kiểu stylesheet và chuyển đổi dữ liệu XML
Lấy bảng định kiểu stylesheet và chuyển đổi dữ liệu XML

Đây không phải là những gì bạn mong đợi, nhưng nếu bạn xem mã nguồn của trang web trong trình duyệt, bạn sẽ chỉ thấy bản XML gốc. Để thấy được kết quả thực sự của việc chuyển đổi, bạn cần thực hiện sự chuyển đổi và tạo ra tệp kết quả. Bạn có thể thực hiện các câu lệnh bằng mã Java như sau (xem Ví dụ 5).

Ví dụ 5. Chuyển đổi tài liệu XML từ câu lệnh
java org.apache.xalan.xslt.Process -IN recipes.xml -XSL basicstylesheet.xsl -out 
result.html

Nếu bạn nhận được một thông báo ClassNotFoundException, thì bạn cần phải tải Apache Xalan về (xem Tài nguyên) và thêm các file JAR vào đường dẫn lớp classpath.

Nếu thực hiện các câu lệnh trong Ví dụ 5 thành công, bạn sẽ nhận được tệp result.html như trong Ví dụ 6.

Ví dụ 6. Kết quả
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/TR/xhtml1/strict">
<head><title>Recipe</title></head>
<body>
<h2>Gush'gosh</h2>
<h3>Ingredients:</h3>
<p>
          1poundhamburger
          1poundelbow macaroni
          2cupsbrown sugar
          1bagchopped onions
          1teaspoondried dill
       </p>
<h3>Directions:</h3>
<p>
          Brown the hamburger.
          Add onions and cook until transparent.
          Add brown sugar and dill.
          Cook and drain pasta.
          Combine meat and pasta.
       </p>
</body></html>

Tôi đã thêm vào một số khoảng trắng để cho dễ đọc, nhưng có đôi điều cần lưu ý ở đây. Đầu tiên, Ví dụ 6 hiển thị thông tin của chỉ một công thức. Thứ 2, các nguyên liệu bị nhồi vào với nhau mà không có khoảng trắng ngăn cách. Bạn không muốn cả hai điều trên. Thật may là chúng ta có thể tạo các nhiều mẫu chi tiết hơn để có được kết quả như mong muốn.


Thêm các mẫu template

Việc chuyển đổi sẽ không cho bạn một kết quả như mong muốn trừ khi bạn cung cấp cho nó một cái mẫu để xuất dữ liệu. Do đó, bạn sẽ học cách để sử dụng các mẫu template trong phần này.

Tạo các mẫu template

Gần như tất cả các bảng định kiểu stylesheet không sử dụng biểu mẫu đơn giản như trình bày ở trên. Thay vào đó, chúng được chia nhỏ thành một loạt các mẫu template, mỗi mẫu được áp dụng cho mỗi kiểu dữ liệu khác nhau. Chúng ta hãy bắt đầu bằng cách chuyển đổi bảng định kiểu stylesheet vảo biểu mẫu (xem Ví dụ 7).

Ví dụ 7. Bảng định kiểu sửa lại
<xsl:stylesheet 
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns="http://www.w3.org/TR/xhtml1/strict">

<xsl:template match="/">
<html>
  <head>
    <title>Recipe</title>
  </head>
  <body>
    <h2><xsl:value-of select="/recipes/recipe/name"/></h2> 
    <h3>Ingredients:</h3>
    <p><xsl:value-of 
select="/recipes/recipe/ingredients"/></p>
    <h3>Directions:</h3>
    <p><xsl:value-of 
select="/recipes/recipe/instructions"/></p>
  </body>
</html>
</xsl:template></xsl:stylesheet>

Tất cả các thông tin ở đây đều giữ nguyên từ ví dụ trước, ngoại trừ rằng bộ xử lý tìm bảng định kiểu stylesheet và bắt đầu chuyển đổi với các mẫu template phù hợp với gốc tài liệu root, như chỉ ra bởi thuộc tính match. Sau đó nó xuất kết quả của mẫu đó, bao gồm tất cả các giá trị, giống như trước. Nếu bạn thực hiện việc chuyển đổi ngay bây giờ, bạn sẽ thấy kết quả giống hệt trong Ví dụ 6.

Nhưng đấy không phải là những gì bạn muốn. Thay vào đó, bạn muốn có khả năng để định dạng các nguyên liệu ingredient và các chỉ dẫn instruction. Để thực hiện, bạn có thể tạo các mẫu riêng biệt cho những thành phần đó và thêm nó vào bảng định kiểu stylesheet (xem Ví dụ 8).

Ví dụ 8. Tạo các mẫu phụ trợ
<xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns="http://www.w3.org/TR/xhtml1/strict">
   
<xsl:template match="/">
<html>
  <head>
    <title>Recipe</title>
  </head>
  <body>
    <h2><xsl:value-of select="/recipes/recipe/name"/></h2> 
    <h3>Ingredients:</h3>
    <p><xsl:apply-templates 
select="/recipes/recipe/ingredients"/></p>
    <h3>Directions:</h3>
    <p><xsl:apply-templates 
select="/recipes/recipe/instructions"/></p>
  </body>
</html>
</xsl:template>

<xsl:template match="ingredients">

   <h3>INGREDIENTS HERE</h3>

</xsl:template>


<xsl:template match="instructions">

   <h3>INSTRUCTIONS HERE</h3>

</xsl:template>

</xsl:stylesheet>

Lưu ý rằng, thay vì đơn thuần xuất value-of của thành phần bạn đang yêu cầu mẫu stylesheet ứng dụng bất cứ mẫu template phù hợp nào vào các thành phần nguyên liệu ingredient và các chỉ dẫn. Sau đó bạn tạo các mẫu template riêng biệt cho từng thành phần, chỉ rõ trong thuộc tính match. Bộ xử lý khi xử lý đến thành phần apply-templates và chọn bất cứ thành phần ingredient nào trong tài liệu. Nó sẽ tìm kiếm một mẫu ingredient template, nếu thấy, nó sẽ xuất kết quả vào mẫu template đó. Nó làm tương tự cho thành phần instructions. Kết quả trông như trong Hình 3.

Hình 3. Áp dụng các mẫu phù hợp vào thành phần ingredient và instruction
Áp dụng các mẫu phù hợp vào thành phần ingredient và instruction

Chà, đã giống hơn một chút, nhưng bạn biết rằng chúng ta có hai công thức, và chúng ta không muốn dồn phần nguyên liệu ingredient của các công thức vào với nhau, và các phần chỉ dẫn của các công thức vào với nhau. Thật may là chúng ta có thể giải quyết vấn đề đó bằng cách tổ chức lại mẫu template một chút.

Mẫu truyền dữ liệu Propagating templates

Để tổ chức dữ liệu của bạn tốt hơn, hãy để ý cách phân chia và truyền mẫu. XSLT được thiết kế để xử lý thông tin một cách lặp đi lặp lại. Ví dụ, nó có thể phân chia thông tin theo các công thức recipe, rồi định dạng các thành phần chỉ dẫn instructio và nguyên liệu ingredient (xem Ví dụ 9).

Ví dụ 9. Phân chia các công thức recipe
<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="http://www.w3.org/TR/xhtml1/strict">

      <xsl:template match="/">
      <html>
        <head>
          <title>Recipe</title>
        </head>
        <body>
         <xsl:apply-templates select="/recipes/recipe"/>
        </body>
      </html>
      </xsl:template>

    <xsl:template match="recipe">

          <h2><xsl:value-of select="./name"/></h2> 
          <h3>Ingredients:</h3>
          <p><xsl:apply-templates select="./ingredients"/></p>
          <h3>Directions:</h3>
          <p><xsl:apply-templates  
select="./instructions"/></p>

      </xsl:template>
      
      <xsl:template match="ingredients">

         <h3>INGREDIENTS HERE</h3>

      </xsl:template>


      <xsl:template match="instructions">

         <h3>INSTRUCTIONS HERE</h3>

      </xsl:template>

      </xsl:stylesheet>

Trong trường hợp này, bảng định kiểu stylesheet xuất trang HTML cơ bản và duyệt qua từng công thức recipe, rồi xuất từng tên, nguyên liệu ingredient, và chỉ dẫn instruction của từng công thức recipe đó. Một lần nữa, XPath sẽ được xem xét trong Xpath nâng cao, nhưng ở đây, các thành phần của công thức recipe recipe trở thành "nút ngữ cảnh" (context node), do đó thuộc tính select của bạn sẽ liên quan đến những nút ngữ cảnh đó, giống như một tệp thì tương quan với một thư mục cụ thể trong hệ thống tệp. Kết quả được hiển thị trong Hình 4.

Hình 4. Các thành phần của công thức recipe trở thành các nút ngữ cảnh context node
Các thành phần của công thức recipe trở thành các nút ngữ cảnh context node

Ổn rồi, định dạng giờ đã gần giống như chúng ta mong đợi. Nhưng bạn vẫn cần phải hiển thị thông tin cụ thể. Để làm việc đó, chỉnh sửa mẫu template của thành phần instruction và ingredient (xem Ví dụ 10).

Ví dụ 10. Xử lý thành phần instruction và ingredient
...
    <xsl:template match="recipe">

          <h2><xsl:value-of select="./name"/></h2> 
          <h3>Ingredients:</h3>
          <p><xsl:apply-templates select="./ingredients"/></p>
          <h3>Directions:</h3>
          <ol><xsl:apply-templates  
select="./instructions"/></ol>

      </xsl:template>

      <xsl:template match="ingredients/ingredient">

         <xsl:value-of select="./qty"/> <xsl:value-of 
select="./unit"/> <xsl:value-of select="./food"/><br />

      </xsl:template>


      <xsl:template match="instructions/instruction">

         <li><xsl:value-of select="."/></li>

      </xsl:template>

      </xsl:stylesheet>

Lưu ý rằng, bạn yêu cầu bộ xử lý áp dụng bất cứ mẫu template phù hợp nào vào thành phần ingredients , nhưng bạn lại chưa có một mẫu template cụ thể cho thành phần đó. Nếu bạn áp dụng một mẫu template mà nó không tồn tại cho một thành phần, thì dữ liệu sẽ không xuất hiện. Đó không phải là vấn đề ở đây.

Thực tế, khi bạn yêu cầu bộ xử lý áp dụng bất cứ mẫu template phù hợp nào vào thành phần ingredients , nó sẽ không chỉ kiểm tra thành phần ingredients , mà còn kiểm tra tất cả các thành phần con của thành phần ingredients. Đây là cách mà nó tìm thấy các mẫu template cho ingredient, mà ở đó các thông tin như số lượng, đơn vị được xuất ra.

Bạn thực hiện tương tự cho thành phần instruction, dịnh dạng chúng như là Ví dụ các mục. Lưu ý rằng bạn tạo ra một Ví dụ có thứ tự trong mẫu recipe chíng, và gửi chúng cho từng quá trình xử lý một.

Kết quả trông giống như trong Hình 5.

Hình 5. Tạo Ví dụ có thứ tự trong mẫu template recipe chính
Tạo Ví dụ có thứ tự trong mẫu template recipe chính

Định dạng bây giờ tất nhiên là giống như những gì bạn mong đợi. Nhưng nếu bạn nhìn kỹ vào kết quả, bạn sẽ thấy vấn đề về khoảng trắng trong thành phần ingredients vẫn tồn tại (xem Ví dụ 11).

Ví dụ 11. Kết quả thô xuất ra
<?xml version="1.0" encoding="UTF-8"?>
<html 
xmlns="http://www.w3.org/TR/xhtml1/strict"><head><title>Recipe
</title></head><body><h2>Gush'gosh</h2><h3>
Ingredients:</h3><p>
          1poundhamburger<br/>
          1poundelbow macaroni<br/>
          2cupsbrown sugar<br/>
          1bagchopped onions<br/>
          1teaspoondried dill<br/>
       </p><h3>Directions:</h3><ol>
          <li>Brown the hamburger.</li>
          <li>Add onions and cook until transparent.</li>
...

Thêm các khoảng trống

Tại sao chúng ta phải thêm các khoảng trống khi mà chúng đã có trong bảng định kiểu stylesheet? Tại sao chúng không xuất hiện? Thực ra có cách để yêu cầu bảng định kiểu giữ nguyên các khoảng trắng (chúng ta sẽ xem xét trong Lặp và nhập) nhưng trong một số trường hợp thì việc thêm các chuỗi ký tự trực tiếp vào kết quả thì dễ hơn (xem Ví dụ 12).

Ví dụ 12. Thêm văn bản
...
      <xsl:template match="ingredients/ingredient">

         <xsl:value-of select="./qty"/><xsl:text> </xsl:text>
         <xsl:value-of select="./unit"/><xsl:text> </xsl:text>
         <xsl:value-of select="./food"/><br />

      </xsl:template>
...

Bằng cách này, các khoảng trống trong dữ liệu sẽ không bị mất. Bạn cũng có thể sử dụng thành phần text để thêm bất kỳ chuỗi ký tự nào vào mẫu template. (Nhớ rằng, đó là văn bản ký tự chứ không phải là các ký tự điều khiển như ký tự xuống dòng.) Kết quả cuối cùng mà bạn mong muốn ở Ví dụ 13.

Ví dụ 13. Kết quả cuối cùng
<?xml version="1.0" encoding="UTF-8"?>
<html 
xmlns="http://www.w3.org/TR/xhtml1/strict"><head><title>Recipe
</title></head><body><h2>Gush'gosh</h2><h3>
Ingredients:</h3><p>
          1 pound hamburger<br/>
          1 pound elbow macaroni<br/>
          2 cups brown sugar<br/>
          1 bag chopped onions<br/>
          1 teaspoon dried dill<br/>
       </p><h3>Directions:</h3><ol>
          <li>Brown the hamburger.</li>
          <li>Add onions and cook until transparent.</li>
          <li>Add brown sugar and dill.</li>
          <li>Cook and drain pasta.</li>
          <li>Combine meat and pasta.</li>
       </ol><h2>A balanced
 breakfast</h2><h3>Ingredients:</h3><p>
          1 cup cereal<br/>
          1 glass orange juice<br/>
          1 cup milk<br/>
          2 slices toast<br/>
       </p><h3>Directions:</h3><ol>
          <li>Combine cereal and milk in bowl.</li>
          <li>Add all ingredients to table.</li>
       </ol></body></html>

Bạn có thể xem kết quả ở Hình 6.

Hình 6. Xử lý các khoảng trống bị thiếu trong dữ liệu
Xử lý các khoảng trống bị thiếu trong dữ liệu

Tiếp theo, bạn sẽ học cách sử dụng XPath để thêm các thông tin cụ thể vào các trang.


Cơ bản về XPath

Để chuyển đổi dữ liệu như bạn đã thấy, bộ xử lý cần phải biết ngôn ngữ XML Path, hay XPath, ngôn ngữ cho phép bạn quyết định dữ liệu nào được truyền đi hay hiển thị. Phần này sẽ giải thích các khái niệm nền tảng của XPath và chỉ cho bạn cách tạo ra các biểu diễn cơ bản.

XPath là gì?

Bạn có thể nhận thấy phần quan trọng để cho bảng định kiều stylesheet làm việc là khả năng lựa chọn một phần cụ thể của tài liệu. Ví dụ, nếu bạn muốn hiển thị phần các chỉ dẫn instruction, bạn cần phải biết cách để tham chiếu đến chúng. Trong XSLT, bạn tham chiếu đến chúng thông qua các biểu thức của XPath.

Một biểu thức XPath có thể chọn một nốt hoặc một tập hợp các nốt, hoặc nó có thể trả lại một giá trị dữ liệu dựa trên một hoặc nhiều nốt trong tài liệu. Thực ra bạn đã sử dụng các biểu thức XPath để lựa chọn một hoặc nhiều nốt bằng cách duyệt qua cấu trúc phân cấp của tài liệu. XPath cung cấp nhiều cách để chọn các nhóm của các nốt dựa trên các mối quan hệ như cha-con, tổ tiên-con cháu. Trong bài này, bạn sẽ xem qua các mối quan hệ đó, gọi là các trục (axes).

Bạn cũng sẽ xem qua các hàm mạnh hơn của XPath. Và bạn sẽ xem qua các thuộc tính predicates, những phát biểu điều kiện cần thiết mà bạn sẽ thêm vào biểu thức XPath của bạn. Ví dụ, trong phần Xác lập ngữ cảnh (Setting the context) bạn đã biết cách để lựa chọn tất cả các thành phần của công thức recipe trong một tài liệu; các thuộc tính predicate cho phép bạn lựa chọn một thành phần dựa trên các điều kiện cụ thể.

Cuối cùng, bạn sẽ xem qua các hàm mạnh hơn nữa của XPath, mà bạn muốn sử dụng trong chương trình của bạn.

Hãy bắt đầu bằng việc xem xét một ngữ cảnh của biểu thức.

Xác lập ngữ cảnh (Setting the context)

Bước đầu tiên để hiểu XPath là hiểu cách mà các kết quả trả về sẽ được xác định thế nào bởi nốt ngữ cảnh hiện tại. Nốt ngữ cảnh cũng giống như là cái biển "Bạn đang ở đây" trong một bản đồ và bạn sẽ di chuyển theo các con đường khác nhau tùy thuộc vào các biểu thức của XPath. Ví dụ, xem xét bảng định kiểu stylesheet đơn giản sau (xem Ví dụ 14).

Ví dụ 14. Hiển thị ngữ cảnh
    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="/">

        <xsl:copy-of select="." />

        </xsl:template>


    </xsl:stylesheet>

Đây là một thành phần XML mới, copy-of. Ở đó value-of xuất đoạn văn bản của một thành phần, copy-of thực hiện việc sao chép, và xuất nốt được tham chiếu bởi thuộc tính select.

Trong trường hợp này, thuộc tính select sử dụng một biểu thức XPath đơn giản nhất có thể. Dấu chấm (.) tham chiếu đến nốt ngữ cảnh context node, giống như bạn tham chiếu đến một thư mục hiện tại (cho dù nó là thư mục gì cũng được) trong một hệ thống tệp. Vì vậy, nếu bạn thực hiện việc chuyển đổi, bạn sẽ nhận được một kết quả như trong Ví dụ 15.

Ví dụ 15. Chuyển đổi đơn giản nhất
        <recipes>
        <recipe recipeId="1">
        <name>Gush'gosh</name>
        <ingredients>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>hamburger </food></ingredient>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>elbow macaroni</food></ingredient>

        <ingredient><qty>2</qty><unit>cups</unit>
        <food>brown sugar</food></ingredient>

        <ingredient><qty>1</qty><unit>bag</unit>
    <food>chopped onions</food></ingredient>

        <ingredient><qty>1</qty><unit>teaspoon</unit>
        <food>dried dill</food></ingredient>
        </ingredients>
        <instructions>
        <instruction>Brown the hamburger.</instruction>
        <instruction>Add onions and cook until transparent.</instruction>
        <instruction>Add brown sugar and dill.</instruction>
        <instruction>Cook and drain pasta.</instruction>
        <instruction>Combine meat and pasta.</instruction>
        </instructions>
        </recipe>
        ...
        </recipes>

Bạn sẽ nhận thấy rằng, nguyên tài liệu được lặp lại; đó là bởi vì nốt ngữ cảnh context node (chỉ ra trong thuộc tính match của mẫu) là gốc của tài liệu (root hoặc /). Nếu bạn thay đổi nốt ngữ cảnh, bạn sẽ nhận được kết quả khác. Ví dụ, bạn có thể xác lập nốt ngữ cảnh context node là công thức recipe đầu tiên (xem Ví dụ 16).

Ví dụ 16. Thay đổi nốt ngữ cảnh
		<xsl:stylesheet version="1.0"
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
		xmlns="http://www.w3.org/TR/xhtml1/strict">

		<xsl:template match="/recipes/recipe">

		<xsl:copy-of select="." />

		</xsl:template>


		</xsl:stylesheet>

Nếu bạn thực hiện việc chuyển đổi bây giờ, bạn sẽ thấy sự khác biệt (xem Ví dụ 17).

Ví dụ 17. Kết quả sau khi thay đổi ngữ cảnh
        <?xml version="1.0" encoding="UTF-8"?>

        <recipe recipeId="1">
        <name>Gush'gosh</name>
        <ingredients>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>hamburger</food></ingredient>

        <ingredient><qty>1</qty><unit>pound</unit>
        <food>elbow macaroni</food></ingredient>

        <ingredient><qty>2</qty><unit>cups</unit>
        <food>brown sugar</food></ingredient>

        <ingredient><qty>1</qty><unit>bag</unit>
        <food>chopped onions</food></ingredient>

        <ingredient><qty>1</qty><unit>teaspoon</unit>
        <food>dried dill</food></ingredient>
        </ingredients>
        <instructions>
        <instruction>Brown the hamburger.</instruction>
        <instruction>Add onions and cook until transparent.</instruction>
        <instruction>Add brown sugar and dill.</instruction>
        <instruction>Cook and drain pasta.</instruction>
        <instruction>Combine meat and pasta.</instruction>
        </instructions>
        </recipe>

        <recipe recipeId="2">
        <name>A balanced breakfast</name>
        <ingredients>

        <ingredient><qty>1</qty><unit>cup</unit>
        <food>cereal</food></ingredient>

        <ingredient><qty>1</qty><unit>glass</unit>
        <food>orange juice</food></ingredient>

        <ingredient><qty>1</qty><unit>cup</unit>
        <food>milk</food></ingredient>

        <ingredient><qty>2</qty><unit>slices</unit>
        <food>toast</food></ingredient>
        </ingredients>
        <instructions>
        <instruction>Combine cereal and milk in bowl.</instruction>
        <instruction>Add all ingredients to table.</instruction>
        </instructions>
        </recipe>

Bởi vì bạn đã thay đổi nốt ngữ cảnh, nên kết quả cũng thay đổi theo. Điều này rất quan trọng nếu bạn muốn chọn một nốt tương quan đến nốt ngữ cảnh. Ví dụ, có thể bạn chỉ muốn chọn tiêu đề của công thức recipe (xem Ví dụ 18).

Ví dụ 18. Chỉ lựa chọn tiêu đề của recipe
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="/recipes/recipe">

        <xsl:copy-of select="./name" />

        </xsl:template>


        </xsl:stylesheet>

Ở đây bạn đang chọn thành phần name ở một cấp ngay dưới nốt ngữ cảnh hiện tại, do đó kết quả sẽ là (xem Ví dụ 19).

Ví dụ 19. Kết quả
        <?xml version="1.0" encoding="UTF-8"?>
        <name>Gush'gosh</name>
        <name>A balanced breakfast</name>

Bạn sẽ học cách chỉ định các nốt bằng các con số sau này, nhưng bây giờ hãy lưu ý rằng bạn có thể di chuyển nốt ngữ cảnh sang công thức recipe thứ 2 (xem Ví dụ 20).

Ví dụ 20. Di chuyển nốt ngữ cảnh thêm một lần nữa
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="/recipes/recipe">
        </xsl:template>

        <xsl:template match="/recipes/recipe[2]">

        <xsl:copy-of select="./name" />

        </xsl:template>


        </xsl:stylesheet>

(Bạn không cần lo lắng về các mẫu template mới; chúng được dùng để ngăn sự xuất hiện của các đoạn văn bản của các công thức recipe khác.)

Bạn có thể thấy rằng kết quả chuyển đổi đã thay đổi theo ngữ cảnh (xem Ví dụ 21).

Ví dụ 21. Kết quả
        <?xml version="1.0" encoding="UTF-8"?>
        <name>A balanced breakfast</name>

Tìm hiểu các trục axes

Bây giờ, bạn đã biết bạn bắt đầu từ nốt ngữ cảnh nào, và bạn cần biết là bạn có thể đi đến những nốt nào. Cho đến bây giờ, bạn đã sử dụng các phát biểu đơn giản của XPath, chúng giống như là cấu trúc cây thư mục trong một hệ thống tệp, nhưng XPath có thể làm được nhiều hơn thế. Ví dụ, trong khi bạn chỉ chọn các nốt con, bạn cũng có thể tìm các nốt cha, ông hay cháu của một trong các nốt con đó.

Để bắt đầu, bạn cần quy định về các ký hiệu. Bạn đã sử dụng biểu mẫu viết tắt và giản lược để yêu cầu các nốt con. Để sử dụng biểu mẫu đầy đủ, bạn cụ thể các biểu thức XPath như child::name thay vì ./name

Trong cả hai trường hợp, bạn chỉ ra bất cứ nốt nào là con của nốt ngữ cảnh, và cũng là một thành phần name. Bạn cũng có thể móc nối chúng với nhau giống như bạn đã làm ở trên, như /child::recipes/child::recipe thay vì /recipes/recipe.

Các nốt cháu, chắt Descendant

Bây giờ, có thể bạn thắc mắc tại sao phải phiền phức với biểu mẫu đầy đủ khi mà biểu mẫu rút gọn dễ dùng hơn. Thực ra, nếu chỉ lựa chọn các nốt con thì không cần dùng biểu thức đầy đủ. Nhưng nếu bạn muộn chọn các nốt khác theo các mối quan hệ khác nhau thì bạn sẽ dùng biểu mẫu đầy đủ. Ví dụ, biểu thức XPath descendant::instruction lựa chọn tất cả các thành phần chỉ dẫn instruction là con và cháu của nốt ngữ cảnh, không chỉ các nốt con. Một lần nữa, bạn có thể gộp các chỉ dẫn instruction. Ví dụ, bạn có thể chọn tất cả các chỉ dẫn instruction của công thức recipe thứ 2: /recipes/recipe[2]/descendant::instruction.

Một biến thể trên trục con cháu (descendant axis) là trục "con cháu hoặc bản thân" (descendant-or-self axis) dùng để yêu cầu tất cả các nốt con cháu của nốt ngữ cảnh và chính bản thân nó. Ví dụ, biểu thức descendant-or-self::instructions chọn tất các các nốt chỉ dẫn instruction cùng cấp hoặc có cấp thấp hơn nốt ngữ cảnh. Cách viết tắt chung cho kiểu trục "con cháu hoặc bản thân" này là //. Nghĩa là, biểu thức này /recipes/recipe[2]//instructions//instructions chọn tất cả các lời chỉ dẫn của công thức recipe thứ hai, và tất cả các lời chỉ dẫn có trong tài liệu một cách tương ứng. Ví dụ thứ hai này rất phổ biến, và hữu dụng khi bạn muốn chọn tất cả các thành phần của một loại dữ liệu cụ thể trong tài liệu.

Các thuộc tính (Attributes)

Một nhiệm vụ phổ biến nữa là nhu cầu chọn một thuộc tính của một thành phần cụ thể. Ví dụ, có thể bạn muốn chọn thuộc tính recipeId của một công thức recipe cụ thể nào đó. Biểu thức /recipes/recipe/attribute::recipeId lựa chọn thuộc tính recipeId của tất cả các thành phần của công thức recipe. Trục này cũng là một biểu thức viết tắt, do đó bạn có thể viết như sau: /recipes/recipe/@recipeId.

Các nốt cha

Đến đây, bạn đã học cách duyệt cây theo chiều đi xuống nhưng bạn cũng có thể duyệt theo chiều đi lên bằng cách chọn cha của một nột cụ thể nào đó. Ví dụ, giả sử nốt ngữ cảnh là một trong các lời chỉ dẫn direction, nhưng bạn muốn xuất recipeId của công thức recipe hiện tại. Bạn có thể thực hiện như sau: ./parent::node()/parent::node()/@recipeId.

Biểu thức này bắt đầu bằng nốt hiện tại, lời chỉ dẫn instruction, sau đó di chuyển tới nốt cha của nó, thành phần instruction, và rồi tới nốt cha của nốt cha (nốt ông) của nó, công thức recipe, và cuối cùng là thuộc tính thích hợp. Tất nhiên, có thể bạn quen với biểu thức rút gọn : ./../../@recipeId.

Bây giờ hãy xác lập một số điều kiện.


XPath nâng cao

Bạn có thể thực hiện những gì bạn muốn chỉ với những kỹ thuật bên trên. Nhưng đôi khi bạn muốn thực hiện những yêu cầu cụ thể hơn. Mục này sẽ hướng dẫn bạn cách dùng các chỉ thị predicate để chọn các nốt với các điều kiện cụ thể, và mục này cũng giới thiệu các hàm dựng sẵn có trong XPath.

Sử dụng các chỉ thị predicate

Có thể bạn không muốn tất cả các nốt mà chỉ một số nốt cụ thể nào đó. Bạn đã gặp trường hợp này ở phía trên khi sử dụng biểu thức /recipes/recipe[2]//instructions. Đó là biểu thức rút gọnn của /recipes/recipe[position() = 2]//instructions và nó có nghĩa là bạn đang yêu cầu bộ xử lý của XPath duyệt qua thành phần của từng công thức recipe và với những thành phần đó nó duyệt qua các thành phần con. Đối với từng thành phần của recipe, hãy kiểm tra và xem nếu biểu thức position() = 2 có đúng không. (Nói cách khác, công thức recipe thứ hai có trong Ví dụ không?) Nếu phát biểu, hay còn gọi là chỉ thị, đó là đúng, bộ xử lý sẽ sử dụng nốt đó và tiếp tục, trả về các lời chỉ dẫn instructions.

Bạn có thể làm được nhiều việc khác nhau với những chỉ thị predicate. Ví dụ, có thể bạn muốn có những công thức có tên: /recipes/recipe[name]. Biểu thức này chỉ kiểm tra sự tồn tại của một thành phần name con của thành phần recipe. Bạn cũng có thể tìm những giá trị cụ thể. Ví dụ, bạn có thể lấy recipe có tên "A balanced breakfast": //recipe[name="A balanced breakfast"].

Lưu ý rằng chỉ thị chỉ yêu cầu bộ xử lý trả lại các nốt thực, do đó trong trường hợp này, thành phần công thức recipe được trả về chứ không phải là tên của nó. Mặt khác, bạn có thể yêu cầu bộ xử lý chỉ trả lại tên của công thức recipe đầu tiên với một trong hai biểu thức sau (xem Ví dụ 22).

Ví dụ 22. Trả lại tên của công thức recipe đầu tiên
        //recipe[@recipeId='1']/name
        //name[parent::recipe[@recipeId='1']]

Trong biểu thức đầu tiên, bạn chọn tất cả các thành phần của công thức recipe, rồi trả lại thành phần mà có thuộc tính recipeId là 1. Khi bạn đã tìm thấy nốt đó, bạn chuyển sang nốt con của nó tên là name và gửi trả giá trị của name về. Trong biểu thức thứ 2, bạn tìm tất cả các thành phần name, rồi chỉ chọn những thành phần name có cha với thuộc tính recipeId là 1. Bạn sẽ nhận được cùng một kết quả bằng một trong hai cách đó (xem Ví dụ 23).

Ví dụ 23. Kết quả
        <?xml version="1.0" encoding="UTF-8"?>
        <name>Gush'gosh</name>

Các hàm

XPath cũng cung cấp một số hàm chức năng. Một số hàm trong các hàm chức năng đó liên quan đến bản thân các nốt, như là những hàm tìm kiếm vị trí, một số hàm khác xử lý chuỗi ký tự, một số là hàm toán học, như hàm tính tổng sums, và một số lại liên quan đến phép tính logic boolean.

Các hàm liên quan đến nốt (Nodeset functions)

Các hàm liên quan đến nốt giúp bạn thực hiện các việc như chọn một node cụ thể dựa trên vị trí của nó. Ví dụ, bạn có thể yêu cầu công thức recipe cuối cùng: //recipe[last()]. Biểu thức chọn tất cả các thành phần của recipe, rồi trả lại cái cuối cùng. Bạn cũng có thể sử dụng các hàm tác động lên biểu thức của XPath, ngược lại với các hàm nằm trong các chỉ thị predicate. Ví dụ, bạn có thể yêu cầu đếm các thành phần của công thức recipe: count(//recipe).

Bạn đã xem qua hàm chỉ vị trí position() và cách thức nó hoạt động. Các hàm liên quan đến các nốt khác bao gồm id(), local-name(), namespace-uri(), và name().

Các hàm xử lý chuỗi ký tự (String functions)

Đa số các hàm xử lý chuỗi chỉ thực hiện các thao tác trên chuỗi ký tự chứ không kiểm tra các ký tự đó, với biểu thức gọi hàm contains(). Hàm contains() sẽ cho bạn biết là một chuỗi có phải là một phần của chuỗi khác lớn hơn không. Ví dụ, bạn có thể lấy các nốt có chứa một chuỗi cụ thể nào đó, như là: //recipe[contains(name, 'breakfast')].

Biểu thức này sẽ trả lại thành phần có chứa chuỗi "breakfast" trong thành phần tên của nó.

Hàm substring() cho phép bạn chọn một khoảng ký tự con cụ thể trong một xâu chuỗi. Ví dụ, biểu thức: substring(//recipe[2]/name, 1, 5) trả lại A bal.

Tham số đầu là chuỗi gốc, tham số thứ hai là vị trí của ký tự đầu tiên muốn lấy, và tham số thứ ba là số lượng ký tự muốn lấy.

Các hàm xử lý chuối khác gồm concat(), substring-before(), substring-after(), starts-with(), và string-length().

Các hàm số học (Numeric functions)

Các hàm số học bao gồm hàm number(), chuyển đổi một giá trị (chuỗi ký tự số chẳng hạn) thành một số mà các hàm số học khác có thể xử lý được. Các hàm số học khác bao gồm sum(), floor(), ceiling(), và round(). Ví dụ, bạn có thể tìm tổng giá trị của tất cả các recipeId với biểu thức: sum(//recipe/@recipeId).

Thực ra, chẳng có lý do gì để thực hiện phép tính đó cả, nhưng recipeID là thuộc tính số duy nhất trong tài liệu ví dụ này.

Hàm floor() tìm số nguyên lớn nhất mà nó nhỏ hơn hoặc bằng giá trị trong ngoặc, trong khi hàm ceiling() trả lại số nguyên nhỏ nhất mà nó lớn hơn hoặc bằng giá trị trong ngoặc. Hàm round() thực hiện phép làm tròn số (xem Ví dụ 24).

Ví dụ 24. Kết quả của các hàm số học
        floor(42.7) = 42
        ceiling(42.7) = 43
        round(42.7) = 43

Hàm logic (Boolean functions)

Các hàm logic được sử dụng trong các biểu thức điều kiện được đề cập trong phần xử lý điều kiện (Conditional processing). Có lẽ, hàm hữu dụng nhất là not(), nó sẽ kiểm tra xem một nốt nào đó có tồn tại hay không. Ví dụ, biểu thức //recipe[contains(name, 'breakfast')] trả lại mọi công thức recipe có chứa chuỗi "breakfast" trong thành phần name của nó. Nhưng nếu bạn muốn có các công thức recipe không chứa breakfast? Bạn có thể sử dụng biểu thức://recipe[not(contains(name, 'breakfast'))].

Các hàm logic khác là true()false(), chúng trả lại các hằng số, và boolean(), chuyển các giá trị bất kỳ thành giá trị logic để sử dụng trong các điều kiện kiểm tra.


Lặp và nhập (Looping and importing)

Bây giờ hãy xem hai khía cạnh quan trọng của việc sử dụng bảng định kiểu XSLT stylesheet: tạo các vòng lặp và nhập các bảng định kiểu khác.

Lặp (Looping)

XSLT là ngôn ngữ theo chức năng chứ không phải theo hành vi, nhưng nó cũng có thể được sử dụng để điều khiển các hoạt động như lặp hay các phép toán có điều kiện. Hãy thử với lặp.

Trong các ví dụ đã học, bạn đã sử dụng các mẫu truyền dữ liệu dựng sẵn của XSLT để áp kiểu cho các thành phần cụ thể. Trong nhiều trường hợp, như thế là ổn. Tuy nhiên, trong một vài tình huống bạn có các tài liệu XML phức tạp, hoặc các nhu cầu phức tạp, thì nó dễ dàng ứng dụng thông tin rõ ràng (xem Ví dụ 25).

Ví dụ 25. Áp kiểu trực tiếp dùng vòng lặp
        <xsl:template match="recipe">

        <h2><xsl:value-of select="./name"/></h2>
        <h3>Ingredients:</h3>
        <p>
        
            <xsl:for-each select="./ingredients/ingredient">
            <xsl:value-of select="./qty"/><xsl:text> </xsl:text>
            <xsl:value-of select="./unit"/><xsl:text> </xsl:text>
            <xsl:value-of select="./food"/><br />
            </xsl:for-each>

        </p>

        <h3>Directions:</h3>
        <ol>

            <xsl:for-each select="./instructions/instruction">
            <li><xsl:value-of select="."/></li>
            </xsl:for-each>

        </ol>

        </xsl:template>

        </xsl:stylesheet>

Cấu trúc vòng lặp này rất giống với cấu trúc for-each. Như tên của nó, mỗi biểu diễn của vòng lặp đều chứa đựng giá trị tiếp theo trong Ví dụ. Giá trị tiếp theo đó có thể là giá trị của phần tử tiếp theo trong mảng; ở đây nó là nốt tiếp theo trong tập các nốt được trả về từ phát biểu XPath trong thuộc tính select. Nghĩa là, khi bạn thực hiện vòng lặp đầu tiên, nốt ngữ cảnh hiện tại là thành phần ingredient đầu tiên. Điều này cho phép bạn chọn só lượng, đơn vị và các thực phẩm con của thành phần đó và thêm chúng vào tài liệu như đã làm với các mẫu template. Đối với các lời chỉ dẫn instruction cũng thế, chỉ khác là chúng được trực tiếp xuất ra.

Các kết quả thu được giống hệt những kết quả trả về thông qua các mẫu template. Mỗi mẫu template được thêm vào tài liệu như một dòng riêng lẻ, nhưng khi bạn cần xử lý các thông tin đó như một mẫu template, bạn sẽ mất các khoảng trắng như đã thấy (xem Hình 7).

Hình 7. Kết quả
Kết quả

Đây là vấn đề trong một số ứng dụng của XML, nhưng vì bạn đang dùng HTML nên nó không phải là vấn đề. Nhưng bạn cần phải nhớ điều này khi quyết định dùng cách nào.

Bao hàm và nhập các bảng định kiểu stylesheet (Including and importing stylesheets)

Một biến thể khác của bảng định kiểu stylesheet liên quan đến cấu trúc của nó. Cho đến bây giờ, tất cả các thông tin của bạn được lưu trong một bảng định stylesheet đơn. Tuy nhiên, trong một số trường hợp, có lẽ bạn muộn tách nó thành các phần nhỏ hơn. Kiểu cấu trúc mô-đun có thể cải tiến khả năng bảo trì, cũng như tính mềm dèo của việc sử dụng các bảng định kiều stylesheet khác nhau cho những mục đích khác nhau. Ví dụ, bạn có thể tạo hai bảng định kiểu stylesheet riêng biệt, một cho ingredients (xem Ví dụ 26).

Ví dụ 26. Tệp ingredients.xsl
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="ingredients/ingredient">

        <xsl:value-of select="./qty"/><xsl:text> </xsl:text>
        <xsl:value-of select="./unit"/><xsl:text> </xsl:text>
        <xsl:value-of select="./food"/><br />

        </xsl:template>

        </xsl:stylesheet>

Bạn cũng có thể tạo một bảng định kiểu stylesheet cho các lời chỉ dẫn instruction (xem Ví dụ 27).

Ví dụ 27. Tệp instructions.xsl
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:template match="instructions/instruction">

        <li><xsl:value-of select="."/></li>

        </xsl:template>

        </xsl:stylesheet>

Các mẫu template giống hệt với những mẫu từ bảng định kiểu stylesheet vốn có. Bạn có thể thêm chúng vào bảng định kiểu stylesheet bằng cách bao hàm chúng (xem Ví dụ 28).

Ví dụ 28. Bao hàm các bảng định kiểu stylesheet
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

         <xsl:include href="ingredients.xsl" /> <xsl:include href="instructions.xsl" /> 

        <xsl:template match="/">
        ...
        </xsl:template>

        <xsl:template match="recipe">

        <h2><xsl:value-of select="./name"/></h2>
        <h3>Ingredients:</h3>
        <p><xsl:apply-templates select="./ingredients"/></p>
        <h3>Directions:</h3>
        <ol><xsl:apply-templates select="./instructions"/></ol>

        </xsl:template>

        </xsl:stylesheet>

Khi các bảng định kiểu stylesheet được bao hàm nằm cùng thư mục với bảng định kiểu chính stylesheet (thực ra không nhất thiết phải thế), thuộc tính While you added the stylesheets to the same directory as the main stylesheet, you don't have to; the href có thể chứa bất của các địa chỉ URL có thể truy cập được. Lưu ý rằng, bạn gửi yêu cầu bộ xử lý tìm kiếm các mẫu template cho các thành phần ingredientsinstructions, mà chúng không có mặt trong tệp này. Tuy nhiên, nếu bạn xử lý bảng định kiểu stylesheet, kết quả chính xác những gì bạn thấy trong các mẫu template phụ trợ được bao hàm trực tiếp, thông qua thành phần include (xem Hình 8).

Hình 8. Kết quả của phương thức bao hàm
Kết quả của phương thức bao hàm

Thành phần include cung cấp hiệu ứng giống như việc thêm nội dung trực tiếp vào bảng định kiểu stylesheet. Về mặt ngữ nghĩa, chúng là một. Mặt khác, bạn có cách khác để bao hàm thông tin: nhập (import).

XSLT cho phép bạn thực hiện nhập một bảng định kiểu stylesheet ngay tại phần đầu của tệp. Tại sao lại ở phần đầu? Bởi vì mục đích của việc nhập một bảng định kiểu stylesheet là cho bạn một lựa chọn để đè lên bất kỳ một mẫu template nào có sẵn. Ví dụ, bạn có thể nhập mẫu ingredient và đè lên nó (xem Ví dụ 29).

Ví dụ 29. Nhập bảng định kiểu stylesheet
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict">

        <xsl:import href="ingredients.xsl" />
        <xsl:import href="instructions.xsl" />

        <xsl:template match="/">
        ...
        </xsl:template>

        <xsl:template match="recipe">
        ...
        </xsl:template>

        
            <xsl:template match="ingredients">

            <ul><xsl:apply-templates select="./ingredient" /></ul>

            </xsl:template>

            <xsl:template match="ingredient">

            <li><xsl:value-of select="./qty"/><xsl:text> 
</xsl:text>
            <xsl:value-of select="./unit"/>
            <xsl:text> </xsl:text><xsl:value-of
            select="./food"/></li>

            </xsl:template>
        

        </xsl:stylesheet>

Ở đây, bạn đã thay thế một mẫu đơn bằng 2 mẫu khác đè lên chúng trong bảng định kiểu được nhập vào (xem Hình 9).

Hình 9. Kết quả của việc nhập bảng định kiểu
Kết quả của việc nhập bảng định kiểu

Lưu ý rằng bạn có thể thu được kết quả tương tự với phương thức bao hàm nếu cả hai phương thức đều xuất hiện. Tuy nhiên, XSLT cho phép bạn sử dụng thuộc tính priority để xác định mẫu template được ưu tiên trước khi thực hiện việc nhập.

Mở rộng XSLT

Bạn đã thấy XSLT có thể được dùng để lập trình. Hãy thử thêm các tính năng Java vào một bảng định kiểu stylesheet.

Đâu tiên phải lưu ý rằng, khi mà các cơ chế mở rộng XSLT được khuyến khích, các cài đặt thực sự mà bạn thấy ở đây là dành cho bộ xử lý XSLT Xalan. Các khái niệm về mở rộng cho các bộ xử lý khác cũng tương tự như vậy, nhưng bạn phải kiểm tra tài liệu đi kèm với bộ xử lý đó để biết cụ thể.

Tiếp theo bạn sẽ tạo phần mở rộng nó cho phép bạn cân dong nguyên liệu của một công thức cho nhiều lần phục vụ.

Các thành phần mở rộng Extension

Mở rộng XSLT yêu cầu các kỹ thuật khác nhau. Đầu tiên là sử dụng thành phần extension. Một thành phần mở rộng extension trong một namespace được chỉ định tương ứng với một lớp đối tượng Java. Ví dụ, hãy xem thành phần mở rộng extension sau (xem Ví dụ 30).

Ví dụ 30. Sử dụng một thành phần extension
       <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        
            xmlns:scaler = "com.backstop.RecipeScaler"
            extension-element-prefixes="scaler">
        

        <xsl:template match="/">
        <html>
        <head>
        <title>Recipes</title>
        </head>
        <body>

        <scaler:scaleMessage servings="2" />

        <xsl:apply-templates select="/recipes/recipe"/>
        </body>
        </html>
        </xsl:template>
        ...

Bạn đã tạo ra một namespace tương ứng với lớp đối tượng comp.backstop.RecipeScaler, nó bao gồm một phương thức tĩnh tên là scaleMessage (xem Ví dụ 31).

Ví dụ 31. Lớp RecipeScaler
        package com.backstop;

        public class RecipeScaler {

        public static String scaleMessage (
        org.apache.xalan.extensions.XSLProcessorContext context,
        org.w3c.dom.Element thisElement){

        return "This recipe has been scaled by a factor of " +
        thisElement.getAttribute("servings") + ".";

        }

        }

Khi bộ xử lý xử lý đến thành phần đó, nó sẽ thấy tiền tố namespace scaler: và biết rằng nó được chỉ định như một tiền tố thành phần mở rộng extension, do đó nó biết lớp được chỉ định trong định nghĩa của namespace. Phương thức được gọi đáp lại tên cục bộ của thành phần đó, scaleMessage. Phương thức đó nhận được hai tham số, bạn sẽ dùng một tham số trong đó. Tham số ngữ cảnh tham chiếu đến ngữ cảnh của bộ xử lý cho phép bạn thấy các thành phần trong thành phần mở rộng extension, nhưng bạn chỉ cần quan tâm đến tham số thứ 2, thành tố extension. Bởi vì nó là tham số sẽ được sử dụng bởi phương thức đó, bạn có thể lấy giá trị của bất cứ thuộc tính nào được thêm vào thành phần, như là servings, trong trường hợp này. Đoạn văn bản trả lại bởi phương thức trên được thêm vào kết quả xuất ra ở vị trí của thành phần mở rộng extension.

Nghĩa là nếu bạn xử lý bảng định kiểu, bạn sẽ nhận được kết quả trong Hình 10.

Hình 10. Kết quả của thành phần mở rộng extension
Kết quả của thành phần mở rộng extension

Thành phần mở rộng có thể là hữu dụng nếu bạn biết dùng.

Các hàm mở rộng (Extension functions)

Một cách khác để thêm các chức năng thông qua bảng định kiểu stylesheet là sử dụng các hàm mở rộng, chúng dễ cài đặt hơn so với các thành phần mở rộng. Ví dụ, bạn có thể tạo một hàm thực hiện phép nhận số lượng nguyên liệu với số lượng thực khách (xem Ví dụ 32).

Ví dụ 32. Phương thức scaleIngredient()
        package com.backstop;

        public class RecipeScaler {

        public static String scaleMessage (
        org.apache.xalan.extensions.XSLProcessorContext context,
        org.w3c.dom.Element thisElement){

        return "This recipe has been scaled by a factor of " +
        thisElement.getAttribute("servings") + ".";

        }

        public static int scaleIngredient(int servings, int original){

        return (servings * original);

        }

        }

Việc thêm hàm này vào bảng định kiểu stylesheet cũng giống như thêm một thành phần mở rộng, mà ở đó bạn đã có namespace cho lớp đó (xem Ví dụ 33).

Ví dụ 33. Thêm hàm mở rộng
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        xmlns:scaler = "com.backstop.RecipeScaler"
        extension-element-prefixes="scaler">
        ...
        <xsl:template match="ingredients/ingredient">

        <xsl:value-of select="scaler:scaleIngredient(2, ./qty)"/>
        <xsl:text> </xsl:text><xsl:value-of select="./unit"/>
        <xsl:text> </xsl:text><xsl:value-of select="./food"/><br
        />

        </xsl:template>
        ...

Lưu ý rằng lời gọi hàm bao gồm cả tiền tố của namespace. Giống như trên, bộ xử lý thấy tiền tố và hiểu rằng nó cẩn thực hiện một lời gọi tới lớp RecipeScaler. Kết quả là các nguyện liệu ingredient được nhân với hai (xem Hình 11).

Hình 11. Sử dụng hàm mở rộng
Sử dụng hàm mở rộng

Nhưng cách này không dễ để bảo trì và phát triển. Giờ hãy xem cách khác dễ hơn.


Lập trình XSLT

Trước khi bắt đầu, bạn cần biết hai khía cạnh của XSLT cung cấp một số khả năng lập trình giống như ngôn ngữ lập trình bình thường khác.

Các biến (varibable) trong XSLT

Thật tốt là bạn đã có cách để thực hiện một hàm, nhưng nó sẽ tốt hơn nếu bạn có thể sử dụng các biến ở ngay đầu trang. Và tất nhiên là bạn có thể (xem Ví dụ 34).

Ví dụ 34. Khai báo và gán giá trị cho biến
    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="http://www.w3.org/TR/xhtml1/strict"
    xmlns:scaler = "com.backstop.RecipeScaler"
    extension-element-prefixes="scaler">

    <xsl:variable name="numberOfServings" select="3" />

    <xsl:template match="/">
    <html>
    <head>
    <title>Recipes</title>
    </head>
    <body>

    <scaler:scaleMessage servings="$numberOfServings" />

    <xsl:apply-templates select="/recipes/recipe"/>
    </body>
    </html>
    </xsl:template>

    <xsl:template match="recipe">
    ...
    </xsl:template>

    <xsl:template match="ingredients/ingredient">

    <xsl:value-of select="scaler:scaleIngredient($numberOfServings,
    ./qty)"/>
    <xsl:text> </xsl:text><xsl:value-of select="./unit"/>
    <xsl:text> </xsl:text><xsl:value-of select="./food"/><br
    />

    </xsl:template>
    ...

XSLT cho phép bạn tạo ra một biến và tham chiếu đến nó với dấu $ như trong Ví dụ 34. Nếu bạn xử lý bảng định kiểu stylesheet, bạn sẽ thấy hai hiệu ứng (xem Hình 12).

Hình 12. Sử dụng biến
sử dụng biến

Lưu ý rằng các nguyện liệu được nhân với số lượng thực khách như ta mong đợi. Tuy nhiên, nếu bạn để ý kỹ, bạn sẽ thấy thành phần mở rộng không được xử lý đúng, nó xử lý biến như là một chuỗi ký tự chứ không phải là giá trị của biến đó. Đây không phải là lỗi; đoạn mã trên không yêu cầu bộ xử lý thực hiện lấy giá trị của biến trước khi xử lý thành phần mở rộng. Do đó bạn phải tiềm cách khác để giải quyết vấn đề.

Xử lý với điều kiện (Conditional processing)

Điều đầu tiên bạn có thể làm là sử dụng quá trình xử lý với điều kiện sao cho bạn hiện thị dữ liệu nếu nó là cần thiết. Ví dụ, xem Ví dụ 35.

Ví dụ 35. Sử dụng thành tố if
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        xmlns:scaler = "com.backstop.RecipeScaler"
        extension-element-prefixes="scaler">

        <xsl:variable name="numberOfServings" select="1" />

        <xsl:template match="/">

        <html>
        <head>
        <title>Recipes</title>
        </head>
        <body>

        <xsl:if test="$numberOfServings > 1">

        <scaler:scaleMessage servings="3" />

        </xsl:if>

        <xsl:apply-templates select="/recipes/recipe"/>
        </body>
        </html>
        </xsl:template>
        ...

Nội dung của thành tố if được chỉ ra ước lượng thuộc tíng test là đúng. Néu không, như trường hợp này, sẽ không có dữ liệu nào được hiển thị (xem Hình 13).

Hình 13. Kết quả của lệnh rẽ nhánh if
kết quả của lệnh rẽ nhánh

Câu lệnh rẽ nhánh trên, nếu giá trị của test lơn hơn một, thành phần mở rộng sẽ hiển thị một giá trị là 3, không thực sự có ý nghĩa lắm. Bạn có thể xử lý các điều kiện rẽ nhánh tốt hơn (xem Ví dụ 36).

Ví dụ 36. Thành tố choose
        <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/TR/xhtml1/strict"
        xmlns:scaler = "com.backstop.RecipeScaler"
        extension-element-prefixes="scaler">

        <xsl:variable name="numberOfServings" select="3" />

        <xsl:template match="/">

        <html>
        <head>
        <title>Recipes</title>
        </head>
        <body>

        
            <xsl:choose>
            <xsl:when test="$numberOfServings < 1">
            Recipes have been scaled by an invalid number.
            </xsl:when>
            <xsl:when test="$numberOfServings = 1">
            </xsl:when>
            <xsl:when test="$numberOfServings = 2">
            <scaler:scaleMessage servings="2" />
            </xsl:when>
            <xsl:when test="$numberOfServings = 3">
        
        <scaler:scaleMessage servings="3" />
        
            </xsl:when>
            <xsl:otherwise>
            Recipes have been scaled for multiple portions.
            </xsl:otherwise>
            </xsl:choose>
        

        <xsl:apply-templates select="/recipes/recipe"/>
        </body>
        </html>
        </xsl:template>
        ...

Trong trường hợp này, bạn có sự kết hợp của câu lệnh rẽ nhánh nếu-thì-ngược_lại if-then-else và lệnh case từ các ngôn ngữ lập trình truyền thống. Thành phần choose hoạt động như một hộp chứa, nhưng thành phần when hiển thị nội dung của nó chỉ khi biểu thức điều kiện của nó là đúng. Cuối cùng, nếu không có thành phần when nào đúng, bộ xử lý sẽ hiển thị nội dung của thành phần otherwise.

Kết quả được hiển thị trong Hình 14.

Hình 14. Kết quả khi sử dụng lệnh rẽ nhánh
Kết quả khi sử dụng lệnh rẽ nhánh

Và bây giờ, bạn có thể hiển thị hoặc tùy biến dữ liệu một cách mềm dẻo.


Tổng kết

Một cách ngắn gọn

Bài viết này đưa bạn đi từ những kiến thức cơ bản nhất của việc chuyển đổi dữ liệu XSL đến việc tạo ra các bảng định kiểu stylesheet tương đối phức tạp. Đầu tiên, bạn học các khái niệm cơ bản của bảng định kiểu stylesheet, sau đó là các biểu thức XPath, một trong những nền tảng của XSLT. Phần cuối của bài này xem xét các khía cạnh phức tạp hơn trong khi dùng bảng định kiểu stylesheet XSLT đó là sử dụng biến, xử lý có điều kiện và mở rộng. Hi vọng rằng, bạn đã có thể thực hiện các nhu cầu của bạn với bảng định kiểu stylesheet XSLT hoặc ít nhất bạn cũng biết cách vượt qua các khúc mắc nếu gặp phải.

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

  • Apache Xalan: tải Xalan và thêm các tệp JAR đi kèm vào đường dẫn lớp classpath để thực hiện các ví dụ trong bài này.
  • Phần mềm dùng thử của IBM: Xây dựng các dự án phát triển tiếp theo của bạn với các phần mềm dùng thử sẵn có từ developerWorks.

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=384646
ArticleTitle=Giới thiệu XSLT
publish-date=05202009