Skip to main content

xbrl_rs/instance/
fact.rs

1//! XBRL fact definitions
2//!
3//! Facts are the actual data values in an XBRL instance document.
4
5use crate::{ExpandedName, XbrlError};
6use std::{fmt, str::FromStr};
7
8/// The numeric accuracy attribute value for a fact (`decimals` or `precision`).
9///
10/// Per XBRL 2.1, the value is either `"INF"` (exact, no rounding) or an integer
11/// representing the number of decimal places (for `decimals`) or significant
12/// digits (for `precision`).
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Decimals {
15    /// An exact value; no rounding tolerance (`"INF"`).
16    Infinite,
17    /// Finite accuracy: the integer value of the `decimals` or `precision` attribute.
18    Finite(i32),
19}
20
21impl FromStr for Decimals {
22    type Err = XbrlError;
23
24    fn from_str(str: &str) -> Result<Self, Self::Err> {
25        if str.eq_ignore_ascii_case("INF") {
26            return Ok(Self::Infinite);
27        }
28        str.parse::<i32>()
29            .map(Self::Finite)
30            .map_err(|_| XbrlError::ParseError {
31                expected: "Decimals",
32                value: str.to_owned(),
33            })
34    }
35}
36
37impl fmt::Display for Decimals {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self {
40            Self::Infinite => f.write_str("INF"),
41            Self::Finite(n) => write!(f, "{n}"),
42        }
43    }
44}
45
46/// Represents a single fact (data point) in an XBRL instance
47#[derive(Debug, Clone)]
48pub enum Fact {
49    /// An item fact with a value and optional unit reference.
50    Item(ItemFact),
51    /// A tuple fact that can contain child facts (both item and tuple facts).
52    /// Needed to preserve the full document structure.
53    Tuple(TupleFact),
54}
55
56/// Represents a single item fact (data point) in an XBRL instance.
57#[derive(Debug, Clone)]
58pub struct ItemFact {
59    /// The the resolved concept name (e.g. "de-gaap-ci:bs.ass.fixAss").
60    concept_name: ExpandedName,
61    /// Optional XML id attribute
62    id: Option<String>,
63    /// Reference to the context ID
64    context_ref: String,
65    /// Optional reference to the unit ID
66    unit_ref: Option<String>,
67    /// The value of the fact
68    value: String,
69    /// Whether the fact is nil (xsi:nil="true")
70    is_nil: bool,
71    /// Decimals attribute for numeric facts
72    decimals: Option<Decimals>,
73    /// Precision attribute for numeric facts
74    precision: Option<Decimals>,
75}
76
77impl ItemFact {
78    #[allow(clippy::too_many_arguments)]
79    pub fn new(
80        id: Option<String>,
81        concept_name: ExpandedName,
82        context_ref: String,
83        unit_ref: Option<String>,
84        value: String,
85        is_nil: bool,
86        decimals: Option<Decimals>,
87        precision: Option<Decimals>,
88    ) -> Self {
89        Self {
90            id,
91            concept_name,
92            context_ref,
93            unit_ref,
94            value,
95            is_nil,
96            decimals,
97            precision,
98        }
99    }
100
101    pub fn concept_name(&self) -> &ExpandedName {
102        &self.concept_name
103    }
104
105    pub fn id(&self) -> Option<&str> {
106        self.id.as_deref()
107    }
108
109    pub fn set_id(&mut self, id: String) {
110        self.id = Some(id);
111    }
112
113    pub fn context_ref(&self) -> &str {
114        &self.context_ref
115    }
116
117    pub fn unit_ref(&self) -> Option<&str> {
118        self.unit_ref.as_deref()
119    }
120
121    pub fn value(&self) -> &str {
122        &self.value
123    }
124
125    pub fn is_nil(&self) -> bool {
126        self.is_nil
127    }
128
129    pub fn set_value(&mut self, value: String) {
130        self.value = value;
131    }
132
133    pub fn set_nil(&mut self, is_nil: bool) {
134        self.is_nil = is_nil;
135    }
136
137    pub fn decimals(&self) -> Option<&Decimals> {
138        self.decimals.as_ref()
139    }
140
141    pub fn set_decimals(&mut self, decimals: Decimals) {
142        self.decimals = Some(decimals);
143    }
144
145    pub fn precision(&self) -> Option<&Decimals> {
146        self.precision.as_ref()
147    }
148
149    pub fn set_precision(&mut self, precision: Decimals) {
150        self.precision = Some(precision);
151    }
152}
153
154/// Represents a tuple fact that can contain child facts.
155#[derive(Debug, Clone)]
156pub struct TupleFact {
157    /// Optional XML id attribute
158    id: Option<String>,
159    /// The resolved concept name (e.g.
160    /// "de-gcd:genInfo.company.id.shareholder").
161    concept_name: ExpandedName,
162    /// Whether the tuple is nil (xsi:nil="true"). A nil tuple has no children
163    /// and its content model constraints (e.g. minOccurs) do not apply.
164    is_nil: bool,
165    /// Nested child facts (item and tuple facts)
166    children: Vec<Fact>,
167}
168
169impl TupleFact {
170    pub fn new(concept_name: ExpandedName) -> Self {
171        Self {
172            id: None,
173            concept_name,
174            is_nil: false,
175            children: Vec::new(),
176        }
177    }
178
179    pub fn concept_name(&self) -> &ExpandedName {
180        &self.concept_name
181    }
182
183    pub fn id(&self) -> Option<&str> {
184        self.id.as_deref()
185    }
186
187    pub fn set_id(&mut self, id: String) {
188        self.id = Some(id);
189    }
190
191    pub fn is_nil(&self) -> bool {
192        self.is_nil
193    }
194
195    pub fn set_nil(&mut self, is_nil: bool) {
196        self.is_nil = is_nil;
197    }
198
199    pub fn children(&self) -> &[Fact] {
200        &self.children
201    }
202
203    pub fn children_mut(&mut self) -> &mut Vec<Fact> {
204        &mut self.children
205    }
206
207    pub fn add_child(&mut self, child: Fact) {
208        self.children.push(child);
209    }
210}
211
212impl Fact {
213    pub fn item(
214        concept: ExpandedName,
215        context_ref: String,
216        unit_ref: Option<String>,
217        value: String,
218    ) -> Self {
219        Self::Item(ItemFact::new(
220            None,
221            concept,
222            context_ref,
223            unit_ref,
224            value,
225            false,
226            None,
227            None,
228        ))
229    }
230
231    pub fn tuple(concept_name: ExpandedName) -> Self {
232        Self::Tuple(TupleFact::new(concept_name))
233    }
234
235    pub fn concept_name(&self) -> &ExpandedName {
236        match self {
237            Self::Item(fact) => fact.concept_name(),
238            Self::Tuple(fact) => fact.concept_name(),
239        }
240    }
241
242    pub fn id(&self) -> Option<&str> {
243        match self {
244            Self::Item(fact) => fact.id(),
245            Self::Tuple(fact) => fact.id(),
246        }
247    }
248
249    pub fn as_item(&self) -> Option<&ItemFact> {
250        match self {
251            Self::Item(fact) => Some(fact),
252            Self::Tuple(_) => None,
253        }
254    }
255
256    pub fn as_item_mut(&mut self) -> Option<&mut ItemFact> {
257        match self {
258            Self::Item(fact) => Some(fact),
259            Self::Tuple(_) => None,
260        }
261    }
262
263    pub fn as_tuple(&self) -> Option<&TupleFact> {
264        match self {
265            Self::Item(_) => None,
266            Self::Tuple(fact) => Some(fact),
267        }
268    }
269
270    pub fn as_tuple_mut(&mut self) -> Option<&mut TupleFact> {
271        match self {
272            Self::Item(_) => None,
273            Self::Tuple(fact) => Some(fact),
274        }
275    }
276
277    pub fn walk_items<'a>(&'a self, out: &mut Vec<&'a ItemFact>) {
278        match self {
279            Self::Item(fact) => out.push(fact),
280            Self::Tuple(fact) => {
281                for child in &fact.children {
282                    child.walk_items(out);
283                }
284            }
285        }
286    }
287
288    pub fn walk_items_mut<'a>(&'a mut self, out: &mut Vec<&'a mut ItemFact>) {
289        match self {
290            Self::Item(fact) => out.push(fact),
291            Self::Tuple(fact) => {
292                for child in &mut fact.children {
293                    child.walk_items_mut(out);
294                }
295            }
296        }
297    }
298
299    pub fn count_items(&self) -> usize {
300        match self {
301            Self::Item(_) => 1,
302            Self::Tuple(tuple_fact) => tuple_fact
303                .children
304                .iter()
305                .map(|fact| fact.count_items())
306                .sum(),
307        }
308    }
309}