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;