1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/*!
This module provides support types for the `DOMImplementation`'s
[`create_document_with_options`](../trait.DOMImplementation.html#method.create_document_with_options).
*/

use std::fmt::{Binary, Display, Formatter, Result};
use std::ops::{BitAnd, BitOr};

// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------

///
/// This type encapsulates a set of options that a client can set that affect the processing of
/// nodes as they are added/removed from the DOM. The default for `ProcessingOptions` is that none
/// of the options are set, if the usual `DOMImplementation::create_document` method turns on any
/// settings these *are not* set by default by `create_document_with_options`.
///
/// This type has a set of methods that turn on options, i.e. `set_assume_ids`,  and retrieve the
/// state of an option, i.e. `has_assume_ids`.
///
/// # Example
///
/// The following will set an option to relax the rules on ID handling. By default the processor
/// will treat all attributes with the prefix and local name `xml:id` _or_ local name `id` and the
/// namespace `http://www.w3.org/XML/1998/namespace` as IDs. With this option set all all attributes
/// with the local name `id` regardless of prefix or namespace will be treated as an ID.
///
/// ```rust
/// use xml_dom::level2::*;
/// use xml_dom::level2::convert::*;
/// use xml_dom::level2::ext::*;
/// use xml_dom::level2::ext::dom_impl::get_implementation_ext;
///
/// let mut options = ProcessingOptions::new();
/// options.set_assume_ids();
///
/// let implementation = get_implementation_ext();
/// let mut document_node = implementation
///     .create_document_with_options(
///         Some("http://www.w3.org/1999/xhtml"),
///         Some("html"),
///         None,
///         options)
///     .unwrap();
/// ```
///
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProcessingOptions(u8);

// ------------------------------------------------------------------------------------------------
// Private Types
// ------------------------------------------------------------------------------------------------

#[doc(hidden)]
#[derive(Clone, Debug)]
#[repr(u8)]
enum ProcessingOptionFlags {
    AssumeIDs = 0b0000_0001,
    ParseEntities = 0b0000_0010,
    AddNamespaces = 0b0000_0100,
}

// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------

impl Default for ProcessingOptions {
    fn default() -> Self {
        Self(0)
    }
}

// ------------------------------------------------------------------------------------------------

impl Display for ProcessingOptions {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "ProcessingOptions {{")?;

        let mut option_strings: Vec<&str> = Vec::new();
        if self.has_assume_ids() {
            option_strings.push("AssumeIDs");
        }
        if self.has_parse_entities() {
            option_strings.push("ParseEntities");
        }
        if self.has_add_namespaces() {
            option_strings.push("AddNamespaces");
        }
        write!(f, "{}", option_strings.join(", "))?;

        write!(f, "}}")
    }
}

// ------------------------------------------------------------------------------------------------

impl Binary for ProcessingOptions {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        if f.alternate() {
            write!(f, "{:#010b}", self.0)
        } else {
            write!(f, "{:08b}", self.0)
        }
    }
}

// ------------------------------------------------------------------------------------------------

impl BitAnd for ProcessingOptions {
    type Output = Self;

    fn bitand(self, rhs: Self) -> Self::Output {
        Self(self.0 & rhs.0)
    }
}

// ------------------------------------------------------------------------------------------------

impl BitOr for ProcessingOptions {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self::Output {
        Self(self.0 | rhs.0)
    }
}

// ------------------------------------------------------------------------------------------------

impl ProcessingOptions {
    ///
    /// Construct a new `ProcessingOptions` instance with all options off.
    ///
    pub fn new() -> Self {
        Default::default()
    }
    ///
    /// Returns true if all options are `false`.
    ///
    pub fn has_none(&self) -> bool {
        self.0 == 0
    }
    ///
    /// Returns `true` if the document will automatically assume certain attributes will be treated
    /// as XML `id` values, else `false`.
    ///
    pub fn has_assume_ids(&self) -> bool {
        self.0 & (ProcessingOptionFlags::AssumeIDs as u8) != 0
    }
    ///
    /// Returns `true` if the document will parse entities inside text nodes and create
    /// `EntityReference` nodes, else `false`.
    ///
    pub fn has_parse_entities(&self) -> bool {
        self.0 & (ProcessingOptionFlags::ParseEntities as u8) != 0
    }
    ///
    /// Returns `true` if the document will automatically add namespace attributes to elements if
    /// qualified names are added that do not have current mappings., else `false`.
    ///
    pub fn has_add_namespaces(&self) -> bool {
        self.0 & (ProcessingOptionFlags::AddNamespaces as u8) != 0
    }
    ///
    /// TBD.
    ///
    /// **Note:** if an attribute with the qualified name `xml:id`, and the namespace is set to the
    /// XML namespace `http://www.w3.org/XML/1998/namespace` then the value is known to be an ID.
    ///
    /// See xml:id Version 1.0, §4 [Processing xml:id Attributes](https://www.w3.org/TR/xml-id/#processing)
    /// for more details.
    ///
    pub fn set_assume_ids(&mut self) {
        self.0 |= ProcessingOptionFlags::AssumeIDs as u8
    }
    ///
    /// TBD
    ///
    pub fn set_parse_entities(&mut self) {
        self.0 |= ProcessingOptionFlags::ParseEntities as u8
    }
    ///
    /// TBD
    ///
    pub fn set_add_namespaces(&mut self) {
        self.0 |= ProcessingOptionFlags::AddNamespaces as u8
    }
}

// ------------------------------------------------------------------------------------------------
// Unit Tests
// ------------------------------------------------------------------------------------------------

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

    #[test]
    fn test_none() {
        let options = ProcessingOptions::default();

        assert!(options.has_none());
        assert!(!options.has_assume_ids());
        assert!(!options.has_parse_entities());
        assert!(!options.has_add_namespaces());

        assert_eq!(format!("{}", options), r"ProcessingOptions {}".to_string());
        assert_eq!(format!("{:b}", options), r"00000000".to_string());
        assert_eq!(format!("{:#b}", options), r"0b00000000".to_string());

        let new_options = ProcessingOptions::new();
        assert_eq!(options, new_options);
    }
}