Skip to content

Ruby XML, XSLT 和 XPath 教程

XML(可扩展标记语言)是一种用于存储和传输数据的标记语言。Ruby提供了强大的XML处理能力,包括解析、生成、转换和查询XML文档。

📋 本章内容

  • XML基础概念
  • Ruby中的XML处理库
  • 解析XML文档
  • 生成XML文档
  • XPath查询
  • XSLT转换
  • 实际应用示例

🔧 XML处理库

Ruby提供了多个XML处理库:

REXML(Ruby内置)

ruby
require 'rexml/document'

Nokogiri(推荐)

ruby
# 需要先安装: gem install nokogiri
require 'nokogiri'

📖 XML基础概念

什么是XML?

XML是一种标记语言,用于描述数据的结构和内容:

xml
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book id="1">
    <title>Ruby编程指南</title>
    <author>张三</author>
    <price currency="CNY">89.00</price>
    <category>编程</category>
  </book>
  <book id="2">
    <title>Web开发实战</title>
    <author>李四</author>
    <price currency="CNY">99.00</price>
    <category>Web开发</category>
  </book>
</bookstore>

🔍 使用REXML解析XML

基本解析

ruby
require 'rexml/document'

# XML字符串
xml_string = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book id="1">
    <title>Ruby编程指南</title>
    <author>张三</author>
    <price>89.00</price>
  </book>
</bookstore>
XML

# 创建文档对象
doc = REXML::Document.new(xml_string)

# 获取根元素
root = doc.root
puts "根元素: #{root.name}"

# 遍历子元素
root.elements.each('book') do |book|
  puts "书籍ID: #{book.attributes['id']}"
  puts "标题: #{book.elements['title'].text}"
  puts "作者: #{book.elements['author'].text}"
  puts "价格: #{book.elements['price'].text}"
  puts "---"
end

从文件读取XML

ruby
require 'rexml/document'

# 从文件读取
file = File.new("books.xml")
doc = REXML::Document.new(file)
file.close

# 处理文档...

🏗️ 生成XML文档

使用REXML生成XML

ruby
require 'rexml/document'

# 创建新文档
doc = REXML::Document.new
doc.add_element('xml-stylesheet', {
  'type' => 'text/xsl',
  'href' => 'books.xsl'
})

# 创建根元素
root = doc.add_element('bookstore')

# 添加书籍
book1 = root.add_element('book', {'id' => '1'})
book1.add_element('title').add_text('Ruby编程指南')
book1.add_element('author').add_text('张三')
book1.add_element('price', {'currency' => 'CNY'}).add_text('89.00')

book2 = root.add_element('book', {'id' => '2'})
book2.add_element('title').add_text('Web开发实战')
book2.add_element('author').add_text('李四')
book2.add_element('price', {'currency' => 'CNY'}).add_text('99.00')

# 输出XML
formatter = REXML::Formatters::Pretty.new
formatter.compact = true
formatter.write(doc, $stdout)

🔎 XPath查询

XPath是用于在XML文档中查找信息的语言。

基本XPath语法

ruby
require 'rexml/document'

xml_string = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book id="1" category="programming">
    <title>Ruby编程指南</title>
    <author>张三</author>
    <price>89.00</price>
  </book>
  <book id="2" category="web">
    <title>Web开发实战</title>
    <author>李四</author>
    <price>99.00</price>
  </book>
</bookstore>
XML

doc = REXML::Document.new(xml_string)

# 查找所有书籍标题
titles = REXML::XPath.match(doc, "//title")
titles.each { |title| puts title.text }

# 查找特定ID的书籍
book = REXML::XPath.first(doc, "//book[@id='1']")
puts "找到书籍: #{book.elements['title'].text}"

# 查找价格大于90的书籍
expensive_books = REXML::XPath.match(doc, "//book[price > 90]")
expensive_books.each do |book|
  puts "昂贵的书: #{book.elements['title'].text}"
end

# 查找编程类别的书籍
programming_books = REXML::XPath.match(doc, "//book[@category='programming']")
programming_books.each do |book|
  puts "编程书籍: #{book.elements['title'].text}"
end

常用XPath表达式

ruby
# 选择根元素
root = REXML::XPath.first(doc, "/bookstore")

# 选择所有book元素
books = REXML::XPath.match(doc, "//book")

# 选择第一个book元素
first_book = REXML::XPath.first(doc, "//book[1]")

# 选择最后一个book元素
last_book = REXML::XPath.first(doc, "//book[last()]")

# 选择有id属性的book元素
books_with_id = REXML::XPath.match(doc, "//book[@id]")

# 选择包含特定文本的元素
ruby_books = REXML::XPath.match(doc, "//book[contains(title, 'Ruby')]")

🔄 XSLT转换

XSLT(可扩展样式表语言转换)用于将XML文档转换为其他格式。

安装libxslt

bash
# Ubuntu/Debian
sudo apt-get install libxslt1-dev

# macOS
brew install libxslt

# 安装Ruby gem
gem install nokogiri

XSLT转换示例

ruby
require 'nokogiri'

# XML数据
xml_string = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book id="1">
    <title>Ruby编程指南</title>
    <author>张三</author>
    <price>89.00</price>
  </book>
  <book id="2">
    <title>Web开发实战</title>
    <author>李四</author>
    <price>99.00</price>
  </book>
</bookstore>
XML

# XSLT样式表
xslt_string = <<-XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <html>
      <head><title>书店目录</title></head>
      <body>
        <h1>书店目录</h1>
        <table border="1">
          <tr>
            <th>ID</th>
            <th>标题</th>
            <th>作者</th>
            <th>价格</th>
          </tr>
          <xsl:for-each select="bookstore/book">
            <tr>
              <td><xsl:value-of select="@id"/></td>
              <td><xsl:value-of select="title"/></td>
              <td><xsl:value-of select="author"/></td>
              <td><xsl:value-of select="price"/></td>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>
XSLT

# 执行转换
xml_doc = Nokogiri::XML(xml_string)
xslt_doc = Nokogiri::XSLT(xslt_string)
result = xslt_doc.transform(xml_doc)

puts result.to_s

🚀 使用Nokogiri(推荐)

Nokogiri是Ruby中最流行的XML/HTML处理库,性能更好,功能更强大。

安装Nokogiri

bash
gem install nokogiri

基本使用

ruby
require 'nokogiri'

# 解析XML
xml_string = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book id="1">
    <title>Ruby编程指南</title>
    <author>张三</author>
    <price currency="CNY">89.00</price>
  </book>
</bookstore>
XML

doc = Nokogiri::XML(xml_string)

# CSS选择器
title = doc.css('title').first
puts "标题: #{title.content}"

# XPath查询
author = doc.xpath('//author').first
puts "作者: #{author.content}"

# 属性访问
book = doc.css('book').first
puts "书籍ID: #{book['id']}"

price = doc.css('price').first
puts "价格: #{price.content} #{price['currency']}"

修改XML

ruby
require 'nokogiri'

doc = Nokogiri::XML(xml_string)

# 修改文本内容
title = doc.css('title').first
title.content = "Ruby高级编程指南"

# 修改属性
book = doc.css('book').first
book['id'] = '100'

# 添加新元素
category = Nokogiri::XML::Node.new('category', doc)
category.content = '编程语言'
book.add_child(category)

puts doc.to_xml

📝 实际应用示例

RSS阅读器

ruby
require 'nokogiri'
require 'open-uri'

class RSSReader
  def initialize(url)
    @url = url
  end

  def read_feed
    doc = Nokogiri::XML(URI.open(@url))
    
    title = doc.css('channel title').first.content
    puts "RSS源: #{title}"
    puts "=" * 50

    doc.css('item').each_with_index do |item, index|
      puts "#{index + 1}. #{item.css('title').first.content}"
      puts "   链接: #{item.css('link').first.content}"
      puts "   时间: #{item.css('pubDate').first&.content}"
      puts "   描述: #{item.css('description').first&.content&.strip}"
      puts "-" * 30
    end
  end
end

# 使用示例
# reader = RSSReader.new('https://example.com/rss.xml')
# reader.read_feed

配置文件处理器

ruby
require 'nokogiri'

class ConfigManager
  def initialize(config_file)
    @config_file = config_file
    @doc = File.exist?(config_file) ? 
           Nokogiri::XML(File.read(config_file)) : 
           create_default_config
  end

  def get_setting(key)
    node = @doc.css("setting[name='#{key}']").first
    node ? node['value'] : nil
  end

  def set_setting(key, value)
    node = @doc.css("setting[name='#{key}']").first
    
    if node
      node['value'] = value
    else
      root = @doc.root
      setting = Nokogiri::XML::Node.new('setting', @doc)
      setting['name'] = key
      setting['value'] = value
      root.add_child(setting)
    end
  end

  def save
    File.write(@config_file, @doc.to_xml)
  end

  private

  def create_default_config
    Nokogiri::XML(<<-XML)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
    XML
  end
end

# 使用示例
config = ConfigManager.new('app_config.xml')
config.set_setting('database_host', 'localhost')
config.set_setting('database_port', '5432')
config.save

puts "数据库主机: #{config.get_setting('database_host')}"

⚠️ 常见问题和注意事项

编码问题

ruby
# 确保正确处理中文编码
xml_string = <<-XML.force_encoding('UTF-8')
<?xml version="1.0" encoding="UTF-8"?>
<data>
  <message>你好,世界!</message>
</data>
XML

doc = Nokogiri::XML(xml_string)
puts doc.css('message').first.content

命名空间处理

ruby
xml_with_namespace = <<-XML
<?xml version="1.0"?>
<root xmlns:book="http://example.com/book">
  <book:title>Ruby编程</book:title>
</root>
XML

doc = Nokogiri::XML(xml_with_namespace)

# 注册命名空间
doc.remove_namespaces! # 简单方法:移除所有命名空间
title = doc.css('title').first.content

# 或者正确处理命名空间
doc = Nokogiri::XML(xml_with_namespace)
title = doc.xpath('//book:title', 'book' => 'http://example.com/book').first.content

性能优化

ruby
# 对于大型XML文件,使用SAX解析器
class BookHandler < Nokogiri::XML::SAX::Document
  def start_element(name, attributes = [])
    if name == 'book'
      @current_book = {}
      attributes.each { |attr| @current_book[attr[0]] = attr[1] }
    end
  end

  def characters(string)
    @current_text = string.strip if string.strip.length > 0
  end

  def end_element(name)
    case name
    when 'title'
      @current_book[:title] = @current_text
    when 'author'
      @current_book[:author] = @current_text
    when 'book'
      puts "处理书籍: #{@current_book}"
    end
  end
end

# 使用SAX解析器
parser = Nokogiri::XML::SAX::Parser.new(BookHandler.new)
parser.parse(File.open('large_books.xml'))

🎯 最佳实践

  1. 选择合适的解析器

    • 小文件:使用DOM解析器(Nokogiri::XML)
    • 大文件:使用SAX解析器
    • HTML:使用Nokogiri::HTML
  2. 错误处理

ruby
begin
  doc = Nokogiri::XML(xml_string)
  if doc.errors.any?
    puts "XML解析错误:"
    doc.errors.each { |error| puts "  #{error}" }
  end
rescue => e
  puts "处理XML时发生错误: #{e.message}"
end
  1. 内存管理
ruby
# 处理完大型文档后释放内存
doc = nil
GC.start

📚 学习资源

通过本章的学习,你已经掌握了Ruby中XML处理的核心技能。这些知识将帮助你处理配置文件、数据交换、Web服务等各种实际应用场景。

本站内容仅供学习和研究使用。