ftml/tree/
mod.rs

1/*
2 * tree/mod.rs
3 *
4 * ftml - Library to parse Wikidot text
5 * Copyright (C) 2019-2025 Wikijump Team
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21pub mod attribute;
22
23mod align;
24mod anchor;
25mod bibliography;
26mod clear_float;
27mod clone;
28mod code;
29mod container;
30mod date;
31mod definition_list;
32mod element;
33mod embed;
34mod heading;
35mod image;
36mod link;
37mod list;
38mod module;
39mod partial;
40mod ruby;
41mod tab;
42mod table;
43mod tag;
44mod variables;
45
46pub use self::align::*;
47pub use self::anchor::*;
48pub use self::attribute::AttributeMap;
49pub use self::bibliography::*;
50pub use self::clear_float::*;
51pub use self::code::CodeBlock;
52pub use self::container::*;
53pub use self::date::DateItem;
54pub use self::definition_list::*;
55pub use self::element::*;
56pub use self::embed::*;
57pub use self::heading::*;
58pub use self::image::*;
59pub use self::link::*;
60pub use self::list::*;
61pub use self::module::*;
62pub use self::partial::*;
63pub use self::ruby::*;
64pub use self::tab::*;
65pub use self::table::*;
66pub use self::tag::*;
67pub use self::variables::*;
68
69use self::clone::{elements_lists_to_owned, elements_to_owned, string_to_owned};
70use crate::parsing::{ParseError, ParseOutcome};
71use std::borrow::Cow;
72use std::ops::Not;
73
74#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
75#[serde(rename_all = "kebab-case")]
76pub struct SyntaxTree<'t> {
77    /// The list of elements that compose this tree.
78    ///
79    /// Note that each `Element<'t>` can contain other elements within it,
80    /// and these as well, etc. This structure composes the depth of the
81    /// syntax tree.
82    pub elements: Vec<Element<'t>>,
83
84    /// The full table of contents for this page.
85    ///
86    /// Depth list conversion happens here, so that depths on the table
87    /// match the heading level.
88    #[serde(default, skip_serializing_if = "Vec::is_empty")]
89    pub table_of_contents: Vec<Element<'t>>,
90
91    /// The full list of HTML blocks for this page.
92    #[serde(default, skip_serializing_if = "Vec::is_empty")]
93    pub html_blocks: Vec<Cow<'t, str>>,
94
95    /// The full list of code blocks for this page.
96    #[serde(default, skip_serializing_if = "Vec::is_empty")]
97    pub code_blocks: Vec<CodeBlock<'t>>,
98
99    /// The full footnote list for this page.
100    #[serde(default, skip_serializing_if = "Vec::is_empty")]
101    pub footnotes: Vec<Vec<Element<'t>>>,
102
103    /// Whether the renderer should add its own footnote block.
104    ///
105    /// This is true if there is no footnote block in the element
106    /// list above, *and* there are footnotes to render.
107    // NOTE: Not::not() here is effectively saying "don't serialize if !value"
108    //       which is just "is false".
109    #[serde(default, skip_serializing_if = "Not::not")]
110    pub needs_footnote_block: bool,
111
112    /// The full list of bibliographies for this page.
113    #[serde(default, skip_serializing_if = "BibliographyList::is_empty")]
114    pub bibliographies: BibliographyList<'t>,
115
116    /// Hint for the size of the wikitext input.
117    ///
118    /// This is an optimization to make rendering large parges slightly faster.
119    #[serde(skip)]
120    pub wikitext_len: usize,
121}
122
123impl<'t> SyntaxTree<'t> {
124    pub(crate) fn from_element_result(
125        elements: Vec<Element<'t>>,
126        errors: Vec<ParseError>,
127        (html_blocks, code_blocks): (Vec<Cow<'t, str>>, Vec<CodeBlock<'t>>),
128        table_of_contents: Vec<Element<'t>>,
129        (footnotes, needs_footnote_block): (Vec<Vec<Element<'t>>>, bool),
130        bibliographies: BibliographyList<'t>,
131        wikitext_len: usize,
132    ) -> ParseOutcome<Self> {
133        let tree = SyntaxTree {
134            elements,
135            table_of_contents,
136            html_blocks,
137            code_blocks,
138            footnotes,
139            needs_footnote_block,
140            bibliographies,
141            wikitext_len,
142        };
143        ParseOutcome::new(tree, errors)
144    }
145
146    pub fn to_owned(&self) -> SyntaxTree<'static> {
147        SyntaxTree {
148            elements: elements_to_owned(&self.elements),
149            table_of_contents: elements_to_owned(&self.table_of_contents),
150            html_blocks: self
151                .html_blocks
152                .iter()
153                .map(|html| string_to_owned(html))
154                .collect(),
155            code_blocks: self
156                .code_blocks
157                .iter()
158                .map(|code| code.to_owned())
159                .collect(),
160            footnotes: elements_lists_to_owned(&self.footnotes),
161            needs_footnote_block: self.needs_footnote_block,
162            bibliographies: self.bibliographies.to_owned(),
163            wikitext_len: self.wikitext_len,
164        }
165    }
166}
167
168#[test]
169fn borrowed_to_owned() {
170    use std::mem;
171
172    let tree_1: SyntaxTree<'_> = SyntaxTree::default();
173    let tree_2: SyntaxTree<'static> = tree_1.to_owned();
174
175    mem::drop(tree_1);
176
177    let tree_3: SyntaxTree<'static> = tree_2.clone();
178
179    mem::drop(tree_3);
180}