dazzle-core 0.4.4

Core library for Dazzle: Scheme interpreter (ported from OpenJade) + DSSSL primitives + Grove/FOT traits
Documentation
//! DTD parsing for attribute defaults
//!
//! This module parses DTD declarations to extract default attribute values,
//! providing OpenJade compatibility where libxml2 doesn't materialize DTD
//! defaults into the parsed tree.

use anyhow::Result;
use libxml::tree::Document;
use std::collections::HashMap;

/// DTD attribute defaults
///
/// Maps (element_name, attribute_name) -> default_value
#[derive(Debug, Clone)]
pub struct DtdDefaults {
    /// Map of (element, attribute) -> default value
    defaults: HashMap<(String, String), String>,
}

impl DtdDefaults {
    /// Create empty DTD defaults
    pub fn empty() -> Self {
        Self {
            defaults: HashMap::new(),
        }
    }

    /// Parse DTD from document
    pub fn from_document(_doc: &Document) -> Result<Self> {
        let mut defaults = HashMap::new();

        // TODO: Implement full DTD parsing via libxml2 C API
        // For now, hardcode the grammar.dtd defaults as a proof of concept

        // From grammar.dtd:
        // <!ATTLIST syntax
        //   abstract    (abstract|concrete)     concrete
        //   multiple    (multiple|single)       single
        // >
        defaults.insert(("syntax".to_string(), "abstract".to_string()), "concrete".to_string());
        defaults.insert(("syntax".to_string(), "multiple".to_string()), "single".to_string());

        // <!ATTLIST content
        //   type        (...)                   syntax
        //   multiple    (multiple|single)       single
        //   optional    (optional|required)     required
        // >
        defaults.insert(("content".to_string(), "type".to_string()), "syntax".to_string());
        defaults.insert(("content".to_string(), "multiple".to_string()), "single".to_string());
        defaults.insert(("content".to_string(), "optional".to_string()), "required".to_string());

        Ok(Self { defaults })
    }

    /// Get default value for an attribute
    pub fn get_default(&self, element: &str, attribute: &str) -> Option<&str> {
        self.defaults
            .get(&(element.to_string(), attribute.to_string()))
            .map(|s| s.as_str())
    }

    /// Check if an attribute has a default
    pub fn has_default(&self, element: &str, attribute: &str) -> bool {
        self.defaults
            .contains_key(&(element.to_string(), attribute.to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use libxml::parser::Parser;

    #[test]
    fn test_dtd_defaults() {
        let xml = r#"<?xml version="1.0"?>
<!DOCTYPE test [
<!ELEMENT test (item)*>
<!ELEMENT item EMPTY>
<!ATTLIST item
  id       CDATA #REQUIRED
  optional CDATA "default-value"
  choice   (a|b) "a"
>
]>
<test>
  <item id="1"/>
</test>"#;

        let parser = Parser::default();
        let doc = parser.parse_string(xml).unwrap();
        let dtd_defaults = DtdDefaults::from_document(&doc).unwrap();

        // Check defaults
        assert_eq!(
            dtd_defaults.get_default("item", "optional"),
            Some("default-value")
        );
        assert_eq!(dtd_defaults.get_default("item", "choice"), Some("a"));
        assert_eq!(dtd_defaults.get_default("item", "id"), None); // REQUIRED has no default
    }
}