Skip to main content

weavatrix_parse/markup/
mod.rs

1//! Structural extraction for HTML and single-file components.
2//!
3//! HTML contributes two kinds of edge. A `<link href>`, `<script src>` or
4//! `<img src>` names another file, which is an ordinary dependency. A `class`
5//! or `id` attribute names a CSS selector, which resolves to whichever
6//! stylesheet declares it - an edge that exists only once both sides are
7//! parsed, and the reason a document and its stylesheet belong in one graph.
8//!
9//! Attributes are read from the token stream, so a tag written inside a
10//! comment contributes nothing, and a `<` inside an attribute value does not
11//! open a tag.
12
13use crate::facts::{Facts, Import, Span};
14use crate::style::selector_use;
15use crate::syntax::Language;
16use crate::token::{Mode, Token, TokenKind, Tokenizer};
17
18/// Extracts structural facts from one document.
19#[must_use]
20pub fn extract(source: &str, language: Language) -> Facts {
21    let tokens = Tokenizer::new(source, language)
22        .mode(Mode::Lite)
23        .collect::<Vec<_>>();
24    let mut state = Extractor {
25        source,
26        tokens: &tokens,
27        language,
28        facts: Facts::default(),
29        tag: String::new(),
30        text_start: None,
31    };
32    state.run();
33    state.facts
34}
35
36/// Elements whose text is a dependency rather than prose.
37///
38/// XML project files name their dependencies in element content rather than in
39/// attributes: a Maven module lists `<module>ui</module>`, and an artifact is
40/// split across `<groupId>` and `<artifactId>`.
41fn names_a_file_in_text(tag: &str) -> bool {
42    matches!(tag, "module" | "include" | "xi:include" | "systemid")
43}
44
45/// Which attribute names a file, given the tag it is written on.
46///
47/// `href` on an anchor is a link to a page rather than a dependency of this
48/// one, so the tag decides, not the attribute alone.
49fn names_a_file(language: Language, tag: &str, attribute: &str) -> bool {
50    if language == Language::Xml {
51        // A project file points at another project or package by attribute:
52        // `<ProjectReference Include="../Lib/Lib.csproj">`, `<xi:include
53        // href="shared.xml">`, `<xsd:import schemaLocation="types.xsd">`.
54        return matches!(
55            attribute,
56            "include" | "href" | "src" | "schemalocation" | "location" | "file" | "path"
57        );
58    }
59    match attribute {
60        "href" => matches!(tag, "link" | "use"),
61        "src" => matches!(
62            tag,
63            "script" | "img" | "iframe" | "audio" | "video" | "source" | "embed" | "track"
64        ),
65        "srcset" | "data-src" => true,
66        _ => false,
67    }
68}
69
70struct Extractor<'source, 'tokens> {
71    source: &'source str,
72    tokens: &'tokens [Token],
73    language: Language,
74    facts: Facts,
75    /// The tag whose attributes are being read.
76    tag: String,
77    /// Where the text of an element whose content names a file begins.
78    text_start: Option<(usize, String)>,
79}
80
81mod extractor;
82
83#[cfg(test)]
84mod tests;