Rust KDL解析库的使用:高效处理KDL文档格式的Rust插件库

Rust KDL解析库的使用:高效处理KDL文档格式的Rust插件库

基础示例

首先展示基本用法,解析一个简单的KDL文档并访问其内容:

use kdl::{KdlDocument, KdlValue};

// 定义一个KDL文档字符串
let doc_str = r#"
hello 1 2 3

// 这是一个注释
world prop="string-value" {
    child 1
    child 2
    child #inf
}
"#;

// 解析KDL文档
let doc: KdlDocument = doc_str.parse().expect("解析KDL失败");

// 验证hello节点的参数
assert_eq!(
    doc.iter_args("hello").collect::<Vec<&KdlValue>>(),
    vec![&1.into(), &2.into(), &3.into()]
);

// 获取world节点的prop属性
assert_eq!(
    doc.get("world").map(|node| &node["prop"]),
    Some(&"string-value".into())
);

// 文档可以无损往返
assert_eq!(doc.to_string(), doc_str);

格式控制示例

展示如何保留自定义格式和注释:

// 包含自定义格式和注释的节点字符串
let node_str = r#"
  // 缩进的注释
  "formatted" 1 /* 块注释 */ \
    2;
"#;

// 创建新文档并添加已解析的节点
let mut doc = kdl::KdlDocument::new();
doc.nodes_mut().push(node_str.parse().unwrap());

// 验证格式被保留
assert_eq!(&doc.to_string(), node_str);

错误处理示例

使用miette提供美观的错误报告:

# 在Cargo.toml中添加依赖
[dependencies]
miette = { version = "5.9.0", features = ["fancy"] }
fn main() -> miette::Result<()> {
    // 故意提供无效的KDL内容(浮点数不合法)
    "foo 1.".parse::<kdl::KdlDocument>()?;
    Ok(())
}

错误输出示例:

Error:
  × Expected valid value.
   ╭────
 1 │ foo 1.
   ·     ─┬
   ·      ╰── invalid float
   ╰────
  help: Floating point numbers must be base 10, and have numbers after the decimal point.

完整示例代码

下面是一个完整的KDL文档处理示例:

use kdl::{KdlDocument, KdlValue};

fn main() -> miette::Result<()> {
    // 定义包配置的KDL文档
    let kdl_data = r#"
package {
    name "my-package"
    version "1.0.0"
    dependencies {
        kdl "6.3.4"
        serde "1.0"
    }
    authors ["Alice <alice@example.com>", "Bob <bob@example.com>"]
}
"#;

    // 1. 解析KDL文档
    let doc: KdlDocument = kdl_data.parse()?;
    
    // 2. 读取包信息
    if let Some(pkg) = doc.get("package") {
        // 获取包名和版本
        println!("Package name: {}", pkg["name"]);
        println!("Version: {}", pkg["version"]);
        
        // 处理依赖项
        if let Some(deps) = pkg.get("dependencies") {
            println!("\nDependencies:");
            for arg in deps.iter_args() {
                println!("- {}", arg);
            }
        }
        
        // 处理作者列表
        if let Some(authors) = pkg.get("authors") {
            println!("\nAuthors:");
            for author in authors.iter_args() {
                println!("- {}", author);
            }
        }
    }
    
    // 3. 修改文档内容
    let mut doc = doc;
    if let Some(pkg) = doc.get_mut("package") {
        // 更新版本号
        pkg.set("version", "1.0.1".into());
    }
    
    // 4. 输出修改后的文档
    println!("\nModified document:");
    println!("{}", doc.to_string());
    
    Ok(())
}

关键特性说明

  1. 格式保留:与基于serde的解析器不同,kdl库会完整保留原始格式、注释和空白字符

  2. 节点操作

    • doc.get("node") 获取特定节点
    • node["prop"] 访问节点属性
    • iter_args() 遍历节点参数
  3. 数值处理

    • 整数自动解析为i128
    • 浮点数解析为f64
    • 支持特殊值 #inf, #-inf, #nan
  4. 错误报告:与miette集成提供详细的错误定位

使用建议

  1. 适合场景:

    • 人工可编辑的配置文件
    • 需要保留格式的文档处理
    • 结构化但灵活性要求高的数据
  2. 性能考虑:

    • 格式保留会带来一定内存开销
    • 对性能敏感场景可考虑解析后转换为原生数据结构

这个库特别适合需要人工维护配置文件的场景,它能确保修改后的文件保持原有的格式和可读性。


1 回复

Rust KDL解析库的使用:高效处理KDL文档格式的Rust插件库

介绍

KDL (KDL Document Language) 是一种类似于XML/JSON的文档格式,但具有更简洁的语法和更强的可读性。Rust的KDL解析库提供了高效解析和处理KDL文档的能力。

这个库的主要特点包括:

  • 完全兼容KDL规范
  • 高性能解析
  • 类型安全的API
  • 支持查询和修改文档
  • 良好的错误处理

安装

在Cargo.toml中添加依赖:

[dependencies]
kdl = "0.6"

基本使用方法

解析KDL文档

use kdl::KdlDocument;

fn main() {
    let input = r#"
        title "Hello, World!"
        author {
            name "John Doe"
            email "john@example.com"
        }
    "#;
    
    let doc: KdlDocument = input.parse().expect("Failed to parse KDL");
    println!("{:#?}", doc);
}

创建KDL文档

use kdl::{KdlDocument, KdlNode};

fn main() {
    let mut doc = KdlDocument::new();
    
    let mut title = KdlNode::new("title");
    title.push("Hello, World!");
    
    let mut author = KdlNode::new("author");
    let mut name = KdlNode::new("name");
    name.push("John Doe");
    author.push_child(name);
    
    doc.nodes_mut().push(title);
    doc.nodes_mut().push(author);
    
    println!("{}", doc);
}

查询节点

use kdl::KdlDocument;

fn main() {
    let input = r#"
        book {
            title "Rust Programming"
            author "Steve Klabnik"
            year 2023
        }
    "#;
    
    let doc: KdlDocument = input.parse().unwrap();
    
    if let Some(book) = doc.get("book") {
        if let Some(title) = book.get("title") {
            println!("Book title: {}", title.get(0).unwrap().value().as_string().unwrap());
        }
    }
}

修改文档

use kdl::KdlDocument;

fn main() {
    let input = r#"
        user {
            name "Alice"
            age 30
        }
    "#;
    
    let mut doc: KdlDocument = input.parse().unwrap();
    
    if let Some(user) = doc.nodes_mut().iter_mut().find(|n| n.name().value() == "user") {
        if let Some(age) = user.get_mut("age") {
            age.entries_mut()[0].set(31);
        }
    }
    
    println!("{}", doc);
}

高级用法

处理复杂类型

use kdl::{KdlDocument, KdlValue};

fn main() {
    let input = r#"
        config {
            servers [
                "192.168.1.1"
                "192.168.1.2"
            ]
            timeout 5.5
            enabled true
        }
    "#;
    
    let doc: KdlDocument = input.parse().unwrap();
    
    if let Some(config) = doc.get("config") {
        if let Some(servers) = config.get("servers") {
            for server in servers.values() {
                println!("Server: {}", server.as_string().unwrap());
            }
        }
        
        if let Some(timeout) = config.get("timeout") {
            println!("Timeout: {}", timeout.get(0).unwrap().value().as_f64().unwrap());
        }
        
        if let Some(enabled) = config.get("enabled") {
            println!("Enabled: {}", enabled.get(0).unwrap().value().as_bool().unwrap());
        }
    }
}

错误处理

use kdl::KdlDocument;
use miette::{Result, IntoDiagnostic};

fn parse_kdl(input: &str) -> Result<KdlDocument> {
    input.parse::<KdlDocument>().into_diagnostic()
}

fn main() -> Result<()> {
    let input = r#"
        invalid-node {
            this-is-not-valid = "missing value"
        }
    "#;
    
    match parse_kdl(input) {
        Ok(doc) => println!("Parsed: {:?}", doc),
        Err(e) => eprintln!("Error: {:?}", e),
    }
    
    Ok(())
}

性能提示

  1. 对于大型文档,考虑使用KdlDocument::parse_fast模式
  2. 重用KdlDocument实例以减少内存分配
  3. 使用getget_mut方法进行精确查询,避免全文档遍历

总结

Rust的KDL解析库提供了强大而灵活的工具来处理KDL文档。无论是简单的配置解析还是复杂的文档处理,这个库都能提供良好的性能和易用性。通过类型安全的API和丰富的查询功能,你可以轻松地将KDL集成到你的Rust项目中。

完整示例代码

// 引入必要的库
use kdl::{KdlDocument, KdlNode, KdlValue};
use miette::{Result, IntoDiagnostic};

fn main() -> Result<()> {
    // 示例1: 解析KDL文档
    println!("=== 示例1: 解析KDL文档 ===");
    let input1 = r#"
        title "Hello, World!"
        author {
            name "John Doe"
            email "john@example.com"
        }
    "#;
    let doc1: KdlDocument = input1.parse().expect("解析KDL失败");
    println!("解析结果:\n{:#?}\n", doc1);

    // 示例2: 创建KDL文档
    println!("=== 示例2: 创建KDL文档 ===");
    let mut doc2 = KdlDocument::new();
    let mut title = KdlNode::new("title");
    title.push("Hello, World!");
    let mut author = KdlNode::new("author");
    let mut name = KdlNode::new("name");
    name.push("John Doe");
    author.push_child(name);
    doc2.nodes_mut().push(title);
    doc2.nodes_mut().push(author);
    println!("创建的文档:\n{}\n", doc2);

    // 示例3: 查询节点
    println!("=== 示例3: 查询节点 ===");
    let input3 = r#"
        book {
            title "Rust Programming"
            author "Steve Klabnik"
            year 2023
        }
    "#;
    let doc3: KdlDocument = input3.parse().unwrap();
    if let Some(book) = doc3.get("book") {
        if let Some(title) = book.get("title") {
            println!("书名: {}\n", title.get(0).unwrap().value().as_string().unwrap());
        }
    }

    // 示例4: 修改文档
    println!("=== 示例4: 修改文档 ===");
    let input4 = r#"
        user {
            name "Alice"
            age 30
        }
    "#;
    let mut doc4: KdlDocument = input4.parse().unwrap();
    if let Some(user) = doc4.nodes_mut().iter_mut().find(|n| n.name().value() == "user") {
        if let Some(age) = user.get_mut("age") {
            age.entries_mut()[0].set(31);
        }
    }
    println!("修改后的文档:\n{}\n", doc4);

    // 示例5: 处理复杂类型
    println!("=== 示例5: 处理复杂类型 ===");
    let input5 = r#"
        config {
            servers [
                "192.168.1.1"
                "192.168.1.2"
            ]
            timeout 5.5
            enabled true
        }
    "#;
    let doc5: KdlDocument = input5.parse().unwrap();
    if let Some(config) = doc5.get("config") {
        if let Some(servers) = config.get("servers") {
            println!("服务器列表:");
            for server in servers.values() {
                println!("- {}", server.as_string().unwrap());
            }
        }
        if let Some(timeout) = config.get("timeout") {
            println!("超时时间: {}秒", timeout.get(0).unwrap().value().as_f64().unwrap());
        }
        if let Some(enabled) = config.get("enabled") {
            println!("是否启用: {}\n", enabled.get(0).unwrap().value().as_bool().unwrap());
        }
    }

    // 示例6: 错误处理
    println!("=== 示例6: 错误处理 ===");
    let input6 = r#"
        invalid-node {
            this-is-not-valid = "missing value"
        }
    "#;
    match parse_kdl(input6) {
        Ok(doc) => println!("解析成功: {:?}", doc),
        Err(e) => println!("解析错误: {:?}\n", e),
    }

    Ok(())
}

fn parse_kdl(input: &str) -> Result<KdlDocument> {
    input.parse::<KdlDocument>().into_diagnostic()
}

这个完整示例展示了KDL解析库的主要功能,包括:

  1. 解析KDL文档
  2. 创建新的KDL文档
  3. 查询和修改文档节点
  4. 处理复杂数据类型
  5. 错误处理

每个示例都包含了详细的注释,方便理解每个步骤的作用。你可以将这段代码复制到你的Rust项目中运行,查看实际效果。

回到顶部