mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 08:07:38 +00:00
refactor(xml): consume lexmount fork instead of vendored parser
Consume lexmount/html5ever at 4574311c63e79bba7d4ddb98cb0562d8dad499ee and remove the XML vendor tree and obsolete ignore exceptions. All 11 production XML source files match the former vendor byte-for-byte; HTML, XML and Stylo still share registry markup5ever 0.40.0 and web_atoms 0.3.0.
Rebased the style stack onto origin/main at ebae4ca6ca.
Validation: Release; cargo fmt --all; strict workspace/all-target/all-feature Clippy; cargo nextest run --no-fail-fast (20413 passed, 18 skipped); 312 paired XML WPT executions and 840 Chromium overflow comparisons with no differences. Full WPT and nine status/subtest-change replays found no new stable regression.
This commit is contained in:
@@ -12,8 +12,6 @@
|
||||
!/vendor/v8-152.2.0/**
|
||||
!/vendor/deno_v8-0.3.0/
|
||||
!/vendor/deno_v8-0.3.0/**
|
||||
!/vendor/xml5ever-0.39.0/
|
||||
!/vendor/xml5ever-0.39.0/**
|
||||
/.moli
|
||||
__pycache__
|
||||
*.pyc
|
||||
|
||||
Generated
+3
-2
@@ -3159,7 +3159,7 @@ dependencies = [
|
||||
"moli-script",
|
||||
"moli-stylesheet-blocking",
|
||||
"url",
|
||||
"xml5ever 0.40.0",
|
||||
"xml5ever 0.40.1",
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
@@ -6863,7 +6863,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xml5ever"
|
||||
version = "0.40.0"
|
||||
version = "0.40.1"
|
||||
source = "git+https://github.com/lexmount/html5ever?rev=4574311c63e79bba7d4ddb98cb0562d8dad499ee#4574311c63e79bba7d4ddb98cb0562d8dad499ee"
|
||||
dependencies = [
|
||||
"log",
|
||||
"markup5ever 0.40.0",
|
||||
|
||||
+1
-2
@@ -92,7 +92,6 @@ members = [
|
||||
"moli-http-cache",
|
||||
"moli-wpt-compat"
|
||||
]
|
||||
exclude = ["vendor/xml5ever-0.40.0"]
|
||||
|
||||
[workspace.package]
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -103,7 +102,7 @@ fontique = { git = "https://github.com/lexmount/parley", rev = "a18cfc3ba6638d44
|
||||
# Share fontations between shaping, raster rendering and PDF font subsetting.
|
||||
subsetter = { git = "https://github.com/lexmount/subsetter", rev = "8d3815245835af309559c3b4a872d6a0a54913a9" }
|
||||
url = { git = "https://github.com/lexmount/rust-url", rev = "ca9fcad2b99f20c30c28a618ad78410f6fd31161" }
|
||||
xml5ever = { path = "vendor/xml5ever-0.40.0" }
|
||||
xml5ever = { git = "https://github.com/lexmount/html5ever", rev = "4574311c63e79bba7d4ddb98cb0562d8dad499ee" }
|
||||
curl = { git = "https://github.com/lexmount/curl-rust", rev = "9d40ca90d8f19ccfd11afe7dd49bc32aab79435c" }
|
||||
curl-sys = { git = "https://github.com/lexmount/curl-rust", rev = "9d40ca90d8f19ccfd11afe7dd49bc32aab79435c" }
|
||||
cookie = { git = "https://github.com/ldm0/cookie-rs", branch = "priority" }
|
||||
|
||||
Vendored
-70
@@ -1,70 +0,0 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.85"
|
||||
name = "xml5ever"
|
||||
version = "0.40.0"
|
||||
authors = ["The xml5ever project developers"]
|
||||
build = false
|
||||
exclude = ["xml5lib-tests/*"]
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Push based streaming parser for XML."
|
||||
homepage = "https://github.com/servo/html5ever/blob/main/xml5ever/README.md"
|
||||
documentation = "https://docs.rs/xml5ever"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"xml",
|
||||
"xml5",
|
||||
"parser",
|
||||
"parsing",
|
||||
]
|
||||
categories = [
|
||||
"parser-implementations",
|
||||
"web-programming",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/servo/html5ever"
|
||||
|
||||
[features]
|
||||
serde = ["markup5ever/serde"]
|
||||
trace_tokenizer = []
|
||||
|
||||
[lib]
|
||||
name = "xml5ever"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "simple_xml_tokenizer"
|
||||
path = "examples/simple_xml_tokenizer.rs"
|
||||
|
||||
[[example]]
|
||||
name = "xml_tokenizer"
|
||||
path = "examples/xml_tokenizer.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "xml5ever"
|
||||
path = "benches/xml5ever.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
|
||||
[dependencies.markup5ever]
|
||||
version = "0.40"
|
||||
|
||||
[dev-dependencies.criterion]
|
||||
version = "0.8"
|
||||
-201
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Vendored
-25
@@ -1,25 +0,0 @@
|
||||
Copyright (c) 2014 The html5ever Project Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
# xml5ever 0.40.0
|
||||
|
||||
This directory contains the source of the crates.io `xml5ever` 0.40.0 release.
|
||||
It is patched locally because Moli needs the tokenizer to preserve CDATA as a
|
||||
distinct token, the XML tree builder to surface CDATA nodes to its sink, and
|
||||
namespace declarations to remain available as DOM attributes after binding.
|
||||
Numeric character references use the XML 1.0 Char production, preserving
|
||||
referenced carriage returns and C1 values without HTML's Windows-1252 mapping.
|
||||
|
||||
The upstream source is licensed under MIT or Apache-2.0; see `LICENSE-MIT` and
|
||||
`LICENSE-APACHE` in this directory.
|
||||
|
||||
Updated with the crates.io 0.40.0 release. The CDATA, namespace attribute,
|
||||
and XML character-reference patches above are retained on the new source.
|
||||
Vendored
-72
@@ -1,72 +0,0 @@
|
||||
# xml5ever
|
||||
|
||||
 
|
||||
[](https://docs.rs/xml5ever)
|
||||
[](https://crates.io/crates/xml5ever)
|
||||
|
||||
[API documentation](https://docs.rs/xml5ever)
|
||||
|
||||
**Warning:** This library is alpha quality, so no guarantees are given.
|
||||
|
||||
This crate provides a push based XML parser library that trades well-formedness for error recovery.
|
||||
|
||||
xml5ever is based largely on [html5ever](https://github.com/servo/html5ever) parser, so if you have experience with html5ever you will be familiar with xml5ever.
|
||||
|
||||
The library is dual licensed under MIT and Apache license.
|
||||
|
||||
# Why you should use xml5ever
|
||||
|
||||
Main use case for this library is when XML is badly formatted, usually from bad XML
|
||||
templates. XML5 tries to handle most common errors, in a manner similar to HTML5.
|
||||
|
||||
## When you should use it?
|
||||
|
||||
- You aren't interested in well-formed documents.
|
||||
- You need to get some info from your data even if it has errors (although not all possible errors are handled).
|
||||
- You want to features like character references or XML namespaces.
|
||||
|
||||
## When you shouldn't use it
|
||||
|
||||
- You need to have your document validated.
|
||||
- You require DTD support.
|
||||
- You require an easy to use parser, with lots of extensions (e.g. XPath, XQuery).
|
||||
- You require a battle tested, industry proven solution.
|
||||
|
||||
# Installation
|
||||
|
||||
Add xml5ever as a dependency:
|
||||
|
||||
```bash
|
||||
cargo add xml5ever
|
||||
```
|
||||
|
||||
# Getting started
|
||||
|
||||
Here is a very simple RcDom backed parser:
|
||||
|
||||
```rust
|
||||
|
||||
let input = "<xml></xml>".to_tendril();
|
||||
|
||||
// To parse XML into a tree form, we need a TreeSink
|
||||
// luckily xml5ever comes with a static RC backed tree represetation.
|
||||
let dom: RcDom = parse(std::iter::once(input), Default::default());
|
||||
|
||||
// Do something with dom
|
||||
|
||||
```
|
||||
The thing that does actual parsing is the `parse` function. It expects an iterator that can be converted into `StrTendril`, so you can use `std::iter::once(input)` or `Some(input).into_iter()` (where `input` is `StrTendril` like structure).
|
||||
|
||||
# Working on xml5ever
|
||||
|
||||
To build examples and tests you need to do something along the lines of:
|
||||
|
||||
```rust
|
||||
git submodule update --init # to fetch xml5lib-tests
|
||||
cargo build
|
||||
cargo test
|
||||
```
|
||||
|
||||
This will fetch tests from outside repository and it will invoke cargo to
|
||||
build and test the crate. If you need docs checkout either [API docs](https://docs.rs/xml5ever) or run `cargo docs`
|
||||
to generate documentation.
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate criterion;
|
||||
extern crate markup5ever;
|
||||
extern crate xml5ever;
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use criterion::Criterion;
|
||||
|
||||
use markup5ever::buffer_queue::BufferQueue;
|
||||
use xml5ever::tendril::*;
|
||||
use xml5ever::tokenizer::{ProcessResult, Token, TokenSink, XmlTokenizer};
|
||||
|
||||
struct Sink;
|
||||
|
||||
impl TokenSink for Sink {
|
||||
type Handle = ();
|
||||
|
||||
fn process_token(&self, token: Token) -> ProcessResult<()> {
|
||||
// Don't use the token, but make sure we don't get
|
||||
// optimized out entirely.
|
||||
std::hint::black_box(token);
|
||||
ProcessResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn run_bench(c: &mut Criterion, name: &str) {
|
||||
let mut path = PathBuf::from("./");
|
||||
path.push("data/bench/");
|
||||
path.push(name);
|
||||
let mut file = fs::File::open(&path).expect("can't open file");
|
||||
|
||||
// Read the file and treat it as an infinitely repeating sequence of characters.
|
||||
let mut file_input = ByteTendril::new();
|
||||
file.read_to_tendril(&mut file_input)
|
||||
.expect("can't read file");
|
||||
let file_input: StrTendril = file_input.try_reinterpret().unwrap();
|
||||
let size = file_input.len();
|
||||
let mut stream = file_input.chars().cycle();
|
||||
|
||||
// Break the input into chunks of 1024 chars (= a few kB).
|
||||
// This simulates reading from the network.
|
||||
let mut input = vec![];
|
||||
let mut total = 0usize;
|
||||
while total < size {
|
||||
// The by_ref() call is important, otherwise we get wrong results!
|
||||
// See rust-lang/rust#18045.
|
||||
let sz = std::cmp::min(1024, size - total);
|
||||
input.push(stream.by_ref().take(sz).collect::<String>().to_tendril());
|
||||
total += sz;
|
||||
}
|
||||
|
||||
let test_name = format!("xml tokenizing {name}");
|
||||
|
||||
c.bench_function(&test_name, move |b| {
|
||||
b.iter(|| {
|
||||
let tok = XmlTokenizer::new(Sink, Default::default());
|
||||
let buffer = BufferQueue::default();
|
||||
// We are doing clone inside the bench function, this is not ideal, but possibly
|
||||
// necessary since our iterator consumes the underlying buffer.
|
||||
for buf in input.clone().into_iter() {
|
||||
buffer.push_back(buf);
|
||||
let _ = tok.feed(&buffer);
|
||||
}
|
||||
let _ = tok.feed(&buffer);
|
||||
tok.end();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn xml5ever_benchmarks(c: &mut Criterion) {
|
||||
run_bench(c, "strong.xml");
|
||||
}
|
||||
|
||||
criterion_group!(benches, xml5ever_benchmarks);
|
||||
criterion_main!(benches);
|
||||
@@ -1 +0,0 @@
|
||||
<strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong><strong>
|
||||
-223
@@ -1,223 +0,0 @@
|
||||
# Examples
|
||||
|
||||
The examples have been designed with [`cargo-script`](https://github.com/DanielKeep/cargo-script) in mind.
|
||||
|
||||
Here I'll just give broad overview how to install [`cargo script`] for Rust 1.5. For more details, check out [cargo-script repository](https://github.com/DanielKeep/cargo-script).
|
||||
|
||||
cargo install cargo-script
|
||||
|
||||
|
||||
# Token printer
|
||||
|
||||
The basis of xml5ever is its tokenizer and tree builder. Roughly speaking tokenizer
|
||||
takes input and returns a set of tokens like comment, processing instruction, start
|
||||
tag, end tag, etc.
|
||||
|
||||
First let's define our dependencies:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
xml5ever = "0.2.0"
|
||||
tendril = "0.1.3"
|
||||
```
|
||||
|
||||
With dependencies declared, we can now make a simple tokenizer sink. First step is to
|
||||
define a [`TokenSink`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html). [`TokenSink`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html) are traits that received stream of [`Tokens`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/enum.Token.html).
|
||||
|
||||
In our case we'll define a unit struct (i.e. a struct without any fields).
|
||||
|
||||
```rust
|
||||
struct SimpleTokenPrinter;
|
||||
```
|
||||
|
||||
To make `SimpleTokenPrinter` a [`TokenSink`](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html), we need to implement [process_token](https://docs.rs/xml5ever/latest/xml5ever/tokenizer/trait.TokenSink.html#tymethod.process_token) method.
|
||||
|
||||
```rust
|
||||
impl TokenSink for SimpleTokenPrinter {
|
||||
fn process_token(&mut self, token: Token) {
|
||||
match token {
|
||||
CharacterTokens(b) => {
|
||||
println!("TEXT: {}", &*b);
|
||||
},
|
||||
NullCharacterToken => print!("NULL"),
|
||||
TagToken(tag) => {
|
||||
println!("{:?} {} ", tag.kind, &*tag.name.local);
|
||||
},
|
||||
ParseError(err) => {
|
||||
println!("ERROR: {}", err);
|
||||
},
|
||||
PIToken(Pi{ref target, ref data}) => {
|
||||
println!("PI : <?{} {}?>", &*target, &*data);
|
||||
},
|
||||
CommentToken(ref comment) => {
|
||||
println!("<!--{:?}-->", &*comment);
|
||||
},
|
||||
EOFToken => {
|
||||
println!("EOF");
|
||||
},
|
||||
DoctypeToken(Doctype{ref name, ref public_id, ..}) => {
|
||||
println!("<!DOCTYPE {:?} {:?}>", &*name, &*public_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, we need some input to process. For input we'll use `stdin`. However, xml5ever `tokenize_to` method only takes `StrTendril`. So we need to construct a
|
||||
[`ByteTendril`](https://docs.rs/tendril/latest/tendril/type.ByteTendril.html) using `ByteTendril::new()`, then read the `stdin` using [`read_to_tendril`](https://docs.rs/tendril/latest/tendril/trait.ReadExt.html#tymethod.read_to_tendril) extension.
|
||||
|
||||
Once that is set, to make `SimpleTokenPrinter` parse the input, call,
|
||||
`tokenize_to` with it as the first parameter, input wrapped in Option for second parameter and XmlToke.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let sink = SimpleTokenPrinter;
|
||||
|
||||
// We need a ByteTendril to read a file
|
||||
let mut input = ByteTendril::new();
|
||||
// Using SliceExt.read_to_tendril we read stdin
|
||||
io::stdin().read_to_tendril(&mut input).unwrap();
|
||||
// For xml5ever we need StrTendril, so we reinterpret it
|
||||
// into StrTendril.
|
||||
//
|
||||
// You might wonder, how does `try_reinterpret` know we
|
||||
// need StrTendril and the answer is type inference based
|
||||
// on `tokenize_xml_to` signature.
|
||||
let input = input.try_reinterpret().unwrap();
|
||||
// Here we create and run tokenizer
|
||||
let mut tok = XmlTokenizer::new(sink, Default::default());
|
||||
// We pass input to parsed.
|
||||
tok.feed(input);
|
||||
|
||||
// tok.end must be invoked for final bytes to be processed.
|
||||
tok.end();
|
||||
}
|
||||
```
|
||||
|
||||
NOTE: `unwrap` causes panic, it's only OK to use in simple examples.
|
||||
|
||||
For full source code check out: [`examples/simple_xml_tokenizer.rs`](https://github.com/servo/html5ever/blob/main/xml5ever/examples/simple_xml_tokenizer.rs)
|
||||
|
||||
Once we have successfully compiled the example we run the example with inline
|
||||
xml
|
||||
|
||||
```bash
|
||||
cargo script simple_xml_tokenizer.rs <<< "<xml>Text with <b>bold words</b>!</xml>"
|
||||
```
|
||||
|
||||
or by sending an [`examples/example.xml`](https://github.com/servo/html5ever/blob/main/xml5ever/examples/example.xml) located in same folder as examples.
|
||||
|
||||
```bash
|
||||
cargo script simple_xml_tokenizer.rs < example.xml
|
||||
```
|
||||
|
||||
# Tree printer
|
||||
|
||||
To actually get an XML document tree from the xml5ever, you need to use a `TreeSink`.
|
||||
`TreeSink` is in many way similar to the TokenSink. Basically, TokenSink takes data
|
||||
and returns list of tokens, while TreeSink takes tokens and returns a tree of parsed
|
||||
XML document. Do note, that this is a simplified explanation and consult
|
||||
documentation for more info.
|
||||
|
||||
Ok, with that in mind, let's build us a TreePrinter. For example if we get an XML
|
||||
file like:
|
||||
|
||||
```xml
|
||||
<student>
|
||||
<first-name>Bobby</first-name>
|
||||
<last-name>Tables</last-name>
|
||||
</student>
|
||||
```
|
||||
|
||||
We'd want a structure similar to this:
|
||||
|
||||
```
|
||||
#document
|
||||
student
|
||||
first-name
|
||||
#text Bobby
|
||||
last-name
|
||||
#text Tables
|
||||
|
||||
```
|
||||
We won't print anything other than element names and text fields. So comments,
|
||||
doctypes and other such elements are ignored.
|
||||
|
||||
First part is similar to making SimpleTokenPrinter:
|
||||
|
||||
```rust
|
||||
// We need to allocate an input tendril for xml5ever
|
||||
let mut input = ByteTendril::new();
|
||||
// Using SliceExt.read_to_tendril functions we can read stdin
|
||||
io::stdin().read_to_tendril(&mut input).unwrap();
|
||||
let input = input.try_reinterpret().unwrap();
|
||||
```
|
||||
|
||||
This time, we need an implementation of [`TreeSink`](https://docs.rs/xml5ever/latest/xml5ever/tree_builder/trait.TreeSink.html). xml5ever comes with a
|
||||
built-in `TreeSink` implementation called [`RcDom`](https://docs.rs/markup5ever_rcdom/latest/markup5ever_rcdom/struct.RcDom.html). To process input into
|
||||
a `TreeSink` we use the following line:
|
||||
|
||||
```rust
|
||||
let dom: RcDom = parse(one_input(input), Default::default());
|
||||
```
|
||||
|
||||
Let's analyze it a bit. First there is `let dom: RcDom`. We need this part,
|
||||
because the type inferencer can't infer which TreeSink implementation we mean
|
||||
in this scenario.
|
||||
|
||||
Function [`one_input`](https://ygg01.github.io/docs/xml5ever/xml5ever/fn.one_input.html) is a convenience function that turns any value into an iterator. In this case
|
||||
it converts a StrTendril into an Iterator over itself.
|
||||
|
||||
Ok, so now that we parsed our tree what with it? Well, for that we might need some
|
||||
kind of function that will help us traverse it. We shall call that function `walk`.
|
||||
|
||||
```rust
|
||||
fn walk(prefix: &str, handle: Handle) {
|
||||
let node = handle.borrow();
|
||||
|
||||
// We print out the prefix before we start
|
||||
print!("{}", prefix);
|
||||
// We are only interested in following nodes:
|
||||
// Document, Text and Element, so our match
|
||||
// reflects that.
|
||||
match node.node {
|
||||
Document
|
||||
=> println!("#document"),
|
||||
|
||||
Text(ref text) => {
|
||||
println!("#text {}", text.escape_default())
|
||||
},
|
||||
|
||||
Element(ref name, _) => {
|
||||
println!("{}", name.local);
|
||||
},
|
||||
|
||||
_ => {},
|
||||
|
||||
}
|
||||
|
||||
// We increase indent in child nodes
|
||||
let new_indent = {
|
||||
let mut temp = String::new();
|
||||
temp.push_str(prefix);
|
||||
temp.push_str(" ");
|
||||
temp
|
||||
};
|
||||
|
||||
for child in node.children.iter()
|
||||
// In order to avoid weird indentation, we filter
|
||||
// only Text/Element nodes.
|
||||
// We don't need to filter Document since its guaranteed
|
||||
// child elements don't contain documents
|
||||
.filter(|child| match child.borrow().node {
|
||||
Text(_) | Element (_, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
) {
|
||||
// Recursion - Yay!
|
||||
walk(&new_indent, child.clone());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For full source code check out: [`examples/xml_tree_printer.rs`](https://github.com/servo/html5ever/blob/main/rcdom/examples/xml_tree_printer.rs)
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE xml PUBLIC "http://www.wc3.org/">
|
||||
<student><first-name>Bobby</first-name><last-name>Tables</last-name></student>
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/usr/bin/env run-cargo-script
|
||||
//! This is a regular crate doc comment, but it also contains a partial
|
||||
//! Cargo manifest. Note the use of a *fenced* code block, and the
|
||||
//! `cargo` "language".
|
||||
//!
|
||||
//! ```cargo
|
||||
//! [dependencies]
|
||||
//! xml5ever = "0.1.1"
|
||||
//! tendril = "0.1.3"
|
||||
//! markup5ever = "0.7.4"
|
||||
//! ```
|
||||
extern crate markup5ever;
|
||||
extern crate xml5ever;
|
||||
|
||||
use std::io;
|
||||
|
||||
use markup5ever::buffer_queue::BufferQueue;
|
||||
use xml5ever::tendril::{ByteTendril, ReadExt};
|
||||
use xml5ever::tokenizer::{Doctype, Pi, ProcessResult, Token, TokenSink, XmlTokenizer};
|
||||
|
||||
struct SimpleTokenPrinter;
|
||||
|
||||
impl TokenSink for SimpleTokenPrinter {
|
||||
type Handle = ();
|
||||
|
||||
fn process_token(&self, token: Token) -> ProcessResult<()> {
|
||||
match token {
|
||||
Token::Characters(b) => {
|
||||
println!("TEXT: {}", &*b);
|
||||
},
|
||||
Token::Cdata(b) => {
|
||||
println!("CDATA: {}", &*b);
|
||||
},
|
||||
Token::NullCharacter => print!("NULL"),
|
||||
Token::Tag(tag) => {
|
||||
println!("{:?} {} ", tag.kind, &*tag.name.local);
|
||||
},
|
||||
Token::ParseError(err) => {
|
||||
println!("ERROR: {err}");
|
||||
},
|
||||
Token::ProcessingInstruction(Pi {
|
||||
ref target,
|
||||
ref data,
|
||||
}) => {
|
||||
println!("PI : <?{target} {data}?>");
|
||||
},
|
||||
Token::Comment(ref comment) => {
|
||||
println!("<!--{comment:?}-->");
|
||||
},
|
||||
Token::EndOfFile => {
|
||||
println!("EOF");
|
||||
},
|
||||
Token::Doctype(Doctype {
|
||||
ref name,
|
||||
ref public_id,
|
||||
..
|
||||
}) => {
|
||||
println!("<!DOCTYPE {name:?} {public_id:?}>");
|
||||
},
|
||||
};
|
||||
ProcessResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Our implementation of TokenSink
|
||||
let sink = SimpleTokenPrinter;
|
||||
|
||||
// We need a ByteTendril to read a file
|
||||
let mut input = ByteTendril::new();
|
||||
|
||||
// Using SliceExt.read_to_tendril we can read stdin
|
||||
io::stdin().read_to_tendril(&mut input).unwrap();
|
||||
// For xml5ever we need StrTendril, so we reinterpret it
|
||||
// into StrTendril.
|
||||
|
||||
// Load input into BufferQueue
|
||||
let input_buffer = BufferQueue::default();
|
||||
input_buffer.push_back(input.try_reinterpret().unwrap());
|
||||
// Here we create and run tokenizer
|
||||
let tok = XmlTokenizer::new(sink, Default::default());
|
||||
let _ = tok.feed(&input_buffer);
|
||||
tok.end();
|
||||
}
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
#!/usr/bin/env run-cargo-script
|
||||
//! This is a regular crate doc comment, but it also contains a partial
|
||||
//! Cargo manifest. Note the use of a *fenced* code block, and the
|
||||
//! `cargo` "language".
|
||||
//!
|
||||
//! ```cargo
|
||||
//! [dependencies]
|
||||
//! xml5ever = "0.2.0"
|
||||
//! tendril = "0.1.3"
|
||||
//! markup5ever = "0.7.4"
|
||||
//! ```
|
||||
extern crate markup5ever;
|
||||
extern crate xml5ever;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::io;
|
||||
|
||||
use markup5ever::buffer_queue::BufferQueue;
|
||||
use xml5ever::tendril::{ByteTendril, ReadExt};
|
||||
use xml5ever::tokenizer::{
|
||||
EmptyTag, EndTag, Pi, ProcessResult, ShortTag, StartTag, Token, TokenSink, XmlTokenizer,
|
||||
XmlTokenizerOpts,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TokenPrinter {
|
||||
in_char_run: Cell<bool>,
|
||||
}
|
||||
|
||||
impl TokenPrinter {
|
||||
fn is_char(&self, is_char: bool) {
|
||||
match (self.in_char_run.get(), is_char) {
|
||||
(false, true) => print!("CHAR : \""),
|
||||
(true, false) => println!("\""),
|
||||
_ => (),
|
||||
}
|
||||
self.in_char_run.set(is_char);
|
||||
}
|
||||
|
||||
fn do_char(&self, c: char) {
|
||||
self.is_char(true);
|
||||
print!("{}", c.escape_default().collect::<String>());
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenSink for TokenPrinter {
|
||||
type Handle = ();
|
||||
|
||||
fn process_token(&self, token: Token) -> ProcessResult<()> {
|
||||
match token {
|
||||
Token::Characters(b) => {
|
||||
for c in b.chars() {
|
||||
self.do_char(c);
|
||||
}
|
||||
},
|
||||
Token::Cdata(b) => {
|
||||
self.is_char(false);
|
||||
println!("CDATA: {}", &*b);
|
||||
},
|
||||
Token::NullCharacter => self.do_char('\0'),
|
||||
Token::Tag(tag) => {
|
||||
self.is_char(false);
|
||||
// This is not proper HTML serialization, of course.
|
||||
match tag.kind {
|
||||
StartTag => print!("TAG : <\x1b[32m{}\x1b[0m", tag.name.local),
|
||||
EndTag => print!("END TAG : <\x1b[31m/{}\x1b[0m", tag.name.local),
|
||||
ShortTag => print!("Short TAG : <\x1b[31m/{}\x1b[0m", tag.name.local),
|
||||
EmptyTag => print!("Empty TAG : <\x1b[31m{}\x1b[0m", tag.name.local),
|
||||
}
|
||||
for attr in tag.attrs.iter() {
|
||||
print!(
|
||||
" \x1b[36m{}\x1b[0m='\x1b[34m{}\x1b[0m'",
|
||||
attr.name.local, attr.value
|
||||
);
|
||||
}
|
||||
if tag.kind == EmptyTag {
|
||||
print!("/");
|
||||
}
|
||||
println!(">");
|
||||
},
|
||||
Token::ParseError(err) => {
|
||||
self.is_char(false);
|
||||
println!("ERROR: {err}");
|
||||
},
|
||||
Token::ProcessingInstruction(Pi { target, data }) => {
|
||||
self.is_char(false);
|
||||
println!("PI : <?{target:?} {data:?}?>");
|
||||
},
|
||||
_ => {
|
||||
self.is_char(false);
|
||||
println!("OTHER: {token:?}");
|
||||
},
|
||||
};
|
||||
|
||||
ProcessResult::Continue
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let sink = TokenPrinter {
|
||||
in_char_run: Cell::new(false),
|
||||
};
|
||||
let mut input = ByteTendril::new();
|
||||
io::stdin().read_to_tendril(&mut input).unwrap();
|
||||
let input_buffer = BufferQueue::default();
|
||||
input_buffer.push_back(input.try_reinterpret().unwrap());
|
||||
|
||||
let tok = XmlTokenizer::new(
|
||||
sink,
|
||||
XmlTokenizerOpts {
|
||||
profile: true,
|
||||
exact_errors: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let _ = tok.feed(&input_buffer);
|
||||
tok.end();
|
||||
tok.sink.is_char(false);
|
||||
}
|
||||
Vendored
-91
@@ -1,91 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::tokenizer::{XmlTokenizer, XmlTokenizerOpts};
|
||||
use crate::tree_builder::{XmlTreeBuilder, XmlTreeBuilderOpts, XmlTreeSink};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::tendril;
|
||||
use crate::tendril::stream::{TendrilSink, Utf8LossyDecoder};
|
||||
use crate::tendril::StrTendril;
|
||||
use markup5ever::buffer_queue::BufferQueue;
|
||||
use markup5ever::TokenizerResult;
|
||||
|
||||
/// All-encompasing parser setting structure.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct XmlParseOpts {
|
||||
/// Xml tokenizer options.
|
||||
pub tokenizer: XmlTokenizerOpts,
|
||||
/// Xml tree builder .
|
||||
pub tree_builder: XmlTreeBuilderOpts,
|
||||
}
|
||||
|
||||
/// Parse and send results to a `TreeSink`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut sink = MySink;
|
||||
/// parse_document(&mut sink, iter::once(my_str), Default::default());
|
||||
/// ```
|
||||
pub fn parse_document<Sink>(sink: Sink, opts: XmlParseOpts) -> XmlParser<Sink>
|
||||
where
|
||||
Sink: XmlTreeSink,
|
||||
{
|
||||
let tb = XmlTreeBuilder::new(sink, opts.tree_builder);
|
||||
let tok = XmlTokenizer::new(tb, opts.tokenizer);
|
||||
XmlParser {
|
||||
tokenizer: tok,
|
||||
input_buffer: BufferQueue::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// An XML parser,
|
||||
/// ready to receive Unicode input through the `tendril::TendrilSink` trait’s methods.
|
||||
pub struct XmlParser<Sink>
|
||||
where
|
||||
Sink: XmlTreeSink,
|
||||
{
|
||||
/// Tokenizer used by XmlParser.
|
||||
pub tokenizer: XmlTokenizer<XmlTreeBuilder<Sink::Handle, Sink>>,
|
||||
/// Input used by XmlParser.
|
||||
pub input_buffer: BufferQueue,
|
||||
}
|
||||
|
||||
impl<Sink: XmlTreeSink> TendrilSink<tendril::fmt::UTF8> for XmlParser<Sink> {
|
||||
type Output = Sink::Output;
|
||||
|
||||
fn process(&mut self, t: StrTendril) {
|
||||
self.input_buffer.push_back(t);
|
||||
// FIXME: Properly support </script> somehow.
|
||||
while let TokenizerResult::Script(_) = self.tokenizer.feed(&self.input_buffer) {}
|
||||
}
|
||||
|
||||
// FIXME: Is it too noisy to report every character decoding error?
|
||||
fn error(&mut self, desc: Cow<'static, str>) {
|
||||
self.tokenizer.sink.sink.parse_error(desc)
|
||||
}
|
||||
|
||||
fn finish(self) -> Self::Output {
|
||||
self.tokenizer.end();
|
||||
self.tokenizer.sink.sink.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Sink: XmlTreeSink> XmlParser<Sink> {
|
||||
/// Wrap this parser into a `TendrilSink` that accepts UTF-8 bytes.
|
||||
///
|
||||
/// Use this when your input is bytes that are known to be in the UTF-8 encoding.
|
||||
/// Decoding is lossy, like `String::from_utf8_lossy`.
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn from_utf8(self) -> Utf8LossyDecoder<Self> {
|
||||
Utf8LossyDecoder::new(self)
|
||||
}
|
||||
}
|
||||
Vendored
-47
@@ -1,47 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! This crate provides a push based XML parser library that
|
||||
//! adheres to XML5 specification. In other words this library
|
||||
//! trades well-formedness for error recovery.
|
||||
//!
|
||||
//! The idea behind this, was to minimize number of errors from
|
||||
//! tools that generate XML (e.g. `S` won't just return `S`
|
||||
//! as text, but will parse it into `S` ).
|
||||
//! You can check out full specification [here](https://ygg01.github.io/xml5_draft/).
|
||||
//!
|
||||
//! What this library provides is a solid XML parser that can:
|
||||
//!
|
||||
//! * Parse somewhat erroneous XML input
|
||||
//! * Provide support for [Numeric character references](https://en.wikipedia.org/wiki/Numeric_character_reference).
|
||||
//! * Provide partial [XML namespace](http://www.w3.org/TR/xml-names11/) support.
|
||||
//! * Provide full set of SVG/MathML entities
|
||||
//!
|
||||
//! What isn't in scope for this library:
|
||||
//!
|
||||
//! * Document Type Definition parsing - this is pretty hard to do right and nowadays, its used
|
||||
//!
|
||||
|
||||
#![crate_name = "xml5ever"]
|
||||
#![crate_type = "dylib"]
|
||||
#![allow(unexpected_cfgs)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub use markup5ever::*;
|
||||
|
||||
pub(crate) mod macros;
|
||||
|
||||
/// Driver
|
||||
pub mod driver;
|
||||
/// Serializer for XML5.
|
||||
pub mod serialize;
|
||||
/// XML5 tokenizer - converts input into tokens
|
||||
pub mod tokenizer;
|
||||
/// XML5 tree builder - converts tokens into a tree like structure
|
||||
pub mod tree_builder;
|
||||
Vendored
-18
@@ -1,18 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
macro_rules! time {
|
||||
($e:expr) => {{
|
||||
let t0 = ::std::time::Instant::now();
|
||||
let result = $e;
|
||||
let dt = t0.elapsed().as_nanos() as u64;
|
||||
(result, dt)
|
||||
}};
|
||||
}
|
||||
pub(crate) use time;
|
||||
-216
@@ -1,216 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::tree_builder::NamespaceMap;
|
||||
use crate::QualName;
|
||||
pub use markup5ever::serialize::{AttrRef, Serialize, Serializer, TraversalScope};
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Struct for setting serializer options.
|
||||
pub struct SerializeOpts {
|
||||
/// Serialize the root node? Default: ChildrenOnly
|
||||
pub traversal_scope: TraversalScope,
|
||||
}
|
||||
|
||||
impl Default for SerializeOpts {
|
||||
fn default() -> SerializeOpts {
|
||||
SerializeOpts {
|
||||
traversal_scope: TraversalScope::ChildrenOnly(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Method for serializing generic node to a given writer.
|
||||
pub fn serialize<Wr, T>(writer: Wr, node: &T, opts: SerializeOpts) -> io::Result<()>
|
||||
where
|
||||
Wr: Write,
|
||||
T: Serialize,
|
||||
{
|
||||
let mut ser = XmlSerializer::new(writer);
|
||||
node.serialize(&mut ser, opts.traversal_scope)
|
||||
}
|
||||
|
||||
/// Struct used for serializing nodes into a text that other XML
|
||||
/// parses can read.
|
||||
///
|
||||
/// Serializer contains a set of functions (start_elem, end_elem...)
|
||||
/// that make parsing nodes easier.
|
||||
pub struct XmlSerializer<Wr> {
|
||||
writer: Wr,
|
||||
namespace_stack: NamespaceMapStack,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NamespaceMapStack(Vec<NamespaceMap>);
|
||||
|
||||
impl NamespaceMapStack {
|
||||
fn new() -> NamespaceMapStack {
|
||||
NamespaceMapStack(vec![])
|
||||
}
|
||||
|
||||
fn push(&mut self, namespace: NamespaceMap) {
|
||||
self.0.push(namespace);
|
||||
}
|
||||
|
||||
fn pop(&mut self) {
|
||||
self.0.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes given text into the Serializer, escaping it,
|
||||
/// depending on where the text is written inside the tag or attribute value.
|
||||
///
|
||||
/// For example
|
||||
///```text
|
||||
/// <tag>'&-quotes'</tag> becomes <tag>'&-quotes'</tag>
|
||||
/// <tag = "'&-quotes'"> becomes <tag = "'&-quotes'"
|
||||
///```
|
||||
fn write_to_buf_escaped<W: Write>(writer: &mut W, text: &str, attr_mode: bool) -> io::Result<()> {
|
||||
for c in text.chars() {
|
||||
match c {
|
||||
'&' => writer.write_all(b"&"),
|
||||
'\'' if attr_mode => writer.write_all(b"'"),
|
||||
'"' if attr_mode => writer.write_all(b"""),
|
||||
'<' if !attr_mode => writer.write_all(b"<"),
|
||||
'>' if !attr_mode => writer.write_all(b">"),
|
||||
c => writer.write_fmt(format_args!("{c}")),
|
||||
}?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_qual_name<W: Write>(writer: &mut W, name: &QualName) -> io::Result<()> {
|
||||
if let Some(ref prefix) = name.prefix {
|
||||
writer.write_all(prefix.as_bytes())?;
|
||||
writer.write_all(b":")?;
|
||||
}
|
||||
|
||||
writer.write_all(name.local.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<Wr: Write> XmlSerializer<Wr> {
|
||||
/// Creates a new Serializier from a writer and given serialization options.
|
||||
pub fn new(writer: Wr) -> Self {
|
||||
XmlSerializer {
|
||||
writer,
|
||||
namespace_stack: NamespaceMapStack::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn qual_name(&mut self, name: &QualName) -> io::Result<()> {
|
||||
self.find_or_insert_ns(name);
|
||||
write_qual_name(&mut self.writer, name)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn qual_attr_name(&mut self, name: &QualName) -> io::Result<()> {
|
||||
self.find_or_insert_ns(name);
|
||||
write_qual_name(&mut self.writer, name)
|
||||
}
|
||||
|
||||
fn find_uri(&self, name: &QualName) -> bool {
|
||||
let mut found = false;
|
||||
for stack in self.namespace_stack.0.iter().rev() {
|
||||
if let Some(Some(el)) = stack.get(&name.prefix) {
|
||||
found = *el == name.ns;
|
||||
break;
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
fn find_or_insert_ns(&mut self, name: &QualName) {
|
||||
if (name.prefix.is_some() || !name.ns.is_empty()) && !self.find_uri(name) {
|
||||
if let Some(last_ns) = self.namespace_stack.0.last_mut() {
|
||||
last_ns.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Wr: Write> Serializer for XmlSerializer<Wr> {
|
||||
/// Serializes given start element into text. Start element contains
|
||||
/// qualified name and an attributes iterator.
|
||||
fn start_elem<'a, AttrIter>(&mut self, name: QualName, attrs: AttrIter) -> io::Result<()>
|
||||
where
|
||||
AttrIter: Iterator<Item = AttrRef<'a>>,
|
||||
{
|
||||
self.namespace_stack.push(NamespaceMap::empty());
|
||||
|
||||
self.writer.write_all(b"<")?;
|
||||
self.qual_name(&name)?;
|
||||
if let Some(current_namespace) = self.namespace_stack.0.last() {
|
||||
for (prefix, url_opt) in current_namespace.get_scope_iter() {
|
||||
self.writer.write_all(b" xmlns")?;
|
||||
if let Some(ref prefix) = *prefix {
|
||||
self.writer.write_all(b":")?;
|
||||
self.writer.write_all(prefix.as_bytes())?;
|
||||
}
|
||||
|
||||
self.writer.write_all(b"=\"")?;
|
||||
let url = if let Some(ref a) = *url_opt {
|
||||
a.as_bytes()
|
||||
} else {
|
||||
b""
|
||||
};
|
||||
self.writer.write_all(url)?;
|
||||
self.writer.write_all(b"\"")?;
|
||||
}
|
||||
}
|
||||
for (name, value) in attrs {
|
||||
self.writer.write_all(b" ")?;
|
||||
self.qual_attr_name(name)?;
|
||||
self.writer.write_all(b"=\"")?;
|
||||
write_to_buf_escaped(&mut self.writer, value, true)?;
|
||||
self.writer.write_all(b"\"")?;
|
||||
}
|
||||
self.writer.write_all(b">")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes given end element into text.
|
||||
fn end_elem(&mut self, name: QualName) -> io::Result<()> {
|
||||
self.namespace_stack.pop();
|
||||
self.writer.write_all(b"</")?;
|
||||
self.qual_name(&name)?;
|
||||
self.writer.write_all(b">")
|
||||
}
|
||||
|
||||
/// Serializes comment into text.
|
||||
fn write_comment(&mut self, text: &str) -> io::Result<()> {
|
||||
self.writer.write_all(b"<!--")?;
|
||||
self.writer.write_all(text.as_bytes())?;
|
||||
self.writer.write_all(b"-->")
|
||||
}
|
||||
|
||||
/// Serializes given doctype
|
||||
fn write_doctype(&mut self, name: &str) -> io::Result<()> {
|
||||
self.writer.write_all(b"<!DOCTYPE ")?;
|
||||
self.writer.write_all(name.as_bytes())?;
|
||||
self.writer.write_all(b">")
|
||||
}
|
||||
|
||||
/// Serializes text for a node or an attributes.
|
||||
fn write_text(&mut self, text: &str) -> io::Result<()> {
|
||||
write_to_buf_escaped(&mut self.writer, text, false)
|
||||
}
|
||||
|
||||
/// Serializes given processing instruction.
|
||||
fn write_processing_instruction(&mut self, target: &str, data: &str) -> io::Result<()> {
|
||||
self.writer.write_all(b"<?")?;
|
||||
self.writer.write_all(target.as_bytes())?;
|
||||
self.writer.write_all(b" ")?;
|
||||
self.writer.write_all(data.as_bytes())?;
|
||||
self.writer.write_all(b"?>")
|
||||
}
|
||||
}
|
||||
@@ -1,451 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use super::{TokenSink, XmlTokenizer};
|
||||
use crate::data;
|
||||
use crate::tendril::StrTendril;
|
||||
use log::debug;
|
||||
use markup5ever::buffer_queue::BufferQueue;
|
||||
use std::borrow::Cow::{self, Borrowed};
|
||||
use std::char::from_u32;
|
||||
|
||||
use self::State::*;
|
||||
pub use self::Status::*;
|
||||
|
||||
//§ tokenizing-character-references
|
||||
pub struct CharRef {
|
||||
/// The resulting character(s)
|
||||
pub chars: [char; 2],
|
||||
|
||||
/// How many slots in `chars` are valid?
|
||||
pub num_chars: u8,
|
||||
}
|
||||
|
||||
pub enum Status {
|
||||
Stuck,
|
||||
Progress,
|
||||
Done,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Begin,
|
||||
Octothorpe,
|
||||
Numeric(u32), // base
|
||||
NumericSemicolon,
|
||||
Named,
|
||||
BogusName,
|
||||
}
|
||||
|
||||
pub struct CharRefTokenizer {
|
||||
state: State,
|
||||
addnl_allowed: Option<char>,
|
||||
result: Option<CharRef>,
|
||||
|
||||
num: u32,
|
||||
num_too_big: bool,
|
||||
seen_digit: bool,
|
||||
hex_marker: Option<char>,
|
||||
|
||||
name_buf_opt: Option<StrTendril>,
|
||||
name_match: Option<(u32, u32)>,
|
||||
name_len: usize,
|
||||
}
|
||||
|
||||
impl CharRefTokenizer {
|
||||
// NB: We assume that we have an additional allowed character iff we're
|
||||
// tokenizing in an attribute value.
|
||||
pub fn new(addnl_allowed: Option<char>) -> CharRefTokenizer {
|
||||
CharRefTokenizer {
|
||||
state: Begin,
|
||||
addnl_allowed,
|
||||
result: None,
|
||||
num: 0,
|
||||
num_too_big: false,
|
||||
seen_digit: false,
|
||||
hex_marker: None,
|
||||
name_buf_opt: None,
|
||||
name_match: None,
|
||||
name_len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// A CharRefTokenizer can only tokenize one character reference,
|
||||
// so this method consumes the tokenizer.
|
||||
pub fn get_result(self) -> CharRef {
|
||||
self.result.expect("get_result called before done")
|
||||
}
|
||||
|
||||
fn name_buf(&self) -> &StrTendril {
|
||||
self.name_buf_opt
|
||||
.as_ref()
|
||||
.expect("name_buf missing in named character reference")
|
||||
}
|
||||
|
||||
fn name_buf_mut(&mut self) -> &mut StrTendril {
|
||||
self.name_buf_opt
|
||||
.as_mut()
|
||||
.expect("name_buf missing in named character reference")
|
||||
}
|
||||
|
||||
fn finish_none(&mut self) -> Status {
|
||||
self.result = Some(CharRef {
|
||||
chars: ['\0', '\0'],
|
||||
num_chars: 0,
|
||||
});
|
||||
Done
|
||||
}
|
||||
|
||||
fn finish_one(&mut self, c: char) -> Status {
|
||||
self.result = Some(CharRef {
|
||||
chars: [c, '\0'],
|
||||
num_chars: 1,
|
||||
});
|
||||
Done
|
||||
}
|
||||
}
|
||||
|
||||
impl CharRefTokenizer {
|
||||
pub fn step<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
if self.result.is_some() {
|
||||
return Done;
|
||||
}
|
||||
|
||||
debug!("char ref tokenizer stepping in state {:?}", self.state);
|
||||
match self.state {
|
||||
Begin => self.do_begin(tokenizer, input),
|
||||
Octothorpe => self.do_octothorpe(tokenizer, input),
|
||||
Numeric(base) => self.do_numeric(tokenizer, base, input),
|
||||
NumericSemicolon => self.do_numeric_semicolon(tokenizer, input),
|
||||
Named => self.do_named(tokenizer, input),
|
||||
BogusName => self.do_bogus_name(tokenizer, input),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_begin<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
match tokenizer.peek(input) {
|
||||
Some('\t' | '\n' | '\x0C' | ' ' | '<' | '&') => self.finish_none(),
|
||||
Some(c) if Some(c) == self.addnl_allowed => self.finish_none(),
|
||||
Some('#') => {
|
||||
tokenizer.discard_char(input);
|
||||
self.state = Octothorpe;
|
||||
Progress
|
||||
},
|
||||
Some(_) => {
|
||||
self.state = Named;
|
||||
self.name_buf_opt = Some(StrTendril::new());
|
||||
Progress
|
||||
},
|
||||
None => Stuck,
|
||||
}
|
||||
}
|
||||
|
||||
fn do_octothorpe<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
match tokenizer.peek(input) {
|
||||
Some(c @ ('x' | 'X')) => {
|
||||
tokenizer.discard_char(input);
|
||||
self.hex_marker = Some(c);
|
||||
self.state = Numeric(16);
|
||||
},
|
||||
Some(_) => {
|
||||
self.hex_marker = None;
|
||||
self.state = Numeric(10);
|
||||
},
|
||||
None => return Stuck,
|
||||
}
|
||||
Progress
|
||||
}
|
||||
|
||||
fn do_numeric<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
base: u32,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
let Some(c) = tokenizer.peek(input) else {
|
||||
return Stuck;
|
||||
};
|
||||
match c.to_digit(base) {
|
||||
Some(n) => {
|
||||
tokenizer.discard_char(input);
|
||||
self.num = self.num.wrapping_mul(base);
|
||||
if self.num > 0x10FFFF {
|
||||
// We might overflow, and the character is definitely invalid.
|
||||
// We still parse digits and semicolon, but don't use the result.
|
||||
self.num_too_big = true;
|
||||
}
|
||||
self.num = self.num.wrapping_add(n);
|
||||
self.seen_digit = true;
|
||||
Progress
|
||||
},
|
||||
|
||||
None if !self.seen_digit => self.unconsume_numeric(tokenizer, input),
|
||||
|
||||
None => {
|
||||
self.state = NumericSemicolon;
|
||||
Progress
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn do_numeric_semicolon<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
match tokenizer.peek(input) {
|
||||
Some(';') => tokenizer.discard_char(input),
|
||||
Some(_) => tokenizer.emit_error(Borrowed(
|
||||
"Semicolon missing after numeric character reference",
|
||||
)),
|
||||
None => return Stuck,
|
||||
};
|
||||
self.finish_numeric(tokenizer)
|
||||
}
|
||||
|
||||
fn unconsume_numeric<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
let mut unconsume = StrTendril::from_char('#');
|
||||
if let Some(c) = self.hex_marker {
|
||||
unconsume.push_char(c);
|
||||
}
|
||||
|
||||
tokenizer.unconsume(input, unconsume);
|
||||
tokenizer.emit_error(Borrowed("Numeric character reference without digits"));
|
||||
self.finish_none()
|
||||
}
|
||||
|
||||
fn finish_numeric<Sink: TokenSink>(&mut self, tokenizer: &XmlTokenizer<Sink>) -> Status {
|
||||
let (c, error) = match self.num {
|
||||
n if !self.num_too_big
|
||||
&& matches!(
|
||||
n,
|
||||
0x9 | 0xa | 0xd | 0x20..=0xd7ff | 0xe000..=0xfffd | 0x10000..=0x10ffff
|
||||
) =>
|
||||
{
|
||||
(
|
||||
from_u32(n).expect("the XML Char production contains only Unicode scalars"),
|
||||
false,
|
||||
)
|
||||
},
|
||||
_ => ('\u{fffd}', true),
|
||||
};
|
||||
|
||||
if error {
|
||||
let msg = if tokenizer.opts.exact_errors {
|
||||
Cow::from(format!(
|
||||
"Invalid numeric character reference value 0x{:06X}",
|
||||
self.num
|
||||
))
|
||||
} else {
|
||||
Cow::from("Invalid numeric character reference")
|
||||
};
|
||||
tokenizer.emit_error(msg);
|
||||
}
|
||||
|
||||
self.finish_one(c)
|
||||
}
|
||||
|
||||
fn do_named<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
let Some(c) = tokenizer.get_char(input) else {
|
||||
return Stuck;
|
||||
};
|
||||
self.name_buf_mut().push_char(c);
|
||||
match data::NAMED_ENTITIES.get(&self.name_buf()[..]) {
|
||||
// We have either a full match or a prefix of one.
|
||||
Some(&m) => {
|
||||
if m.0 != 0 {
|
||||
// We have a full match, but there might be a longer one to come.
|
||||
self.name_match = Some(m);
|
||||
self.name_len = self.name_buf().len();
|
||||
}
|
||||
// Otherwise we just have a prefix match.
|
||||
Progress
|
||||
},
|
||||
|
||||
// Can't continue the match.
|
||||
None => self.finish_named(tokenizer, Some(c), input),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_name_error<Sink: TokenSink>(&mut self, tokenizer: &XmlTokenizer<Sink>) {
|
||||
let msg = if tokenizer.opts.exact_errors {
|
||||
Cow::from(format!("Invalid character reference &{}", self.name_buf()))
|
||||
} else {
|
||||
Cow::from("Invalid character reference")
|
||||
};
|
||||
tokenizer.emit_error(msg);
|
||||
}
|
||||
|
||||
fn unconsume_name<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) {
|
||||
tokenizer.unconsume(input, self.name_buf_opt.take().unwrap());
|
||||
}
|
||||
|
||||
fn finish_named<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
end_char: Option<char>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
match self.name_match {
|
||||
None => {
|
||||
match end_char {
|
||||
Some(c) if c.is_ascii_alphanumeric() => {
|
||||
// Keep looking for a semicolon, to determine whether
|
||||
// we emit a parse error.
|
||||
self.state = BogusName;
|
||||
return Progress;
|
||||
},
|
||||
|
||||
// Check length because &; is not a parse error.
|
||||
Some(';') if self.name_buf().len() > 1 => self.emit_name_error(tokenizer),
|
||||
|
||||
_ => (),
|
||||
}
|
||||
self.unconsume_name(tokenizer, input);
|
||||
self.finish_none()
|
||||
},
|
||||
|
||||
Some((c1, c2)) => {
|
||||
// We have a complete match, but we may have consumed
|
||||
// additional characters into self.name_buf. Usually
|
||||
// at least one, but several in cases like
|
||||
//
|
||||
// ¬ => match for U+00AC
|
||||
// ¬i => valid prefix for ¬in
|
||||
// ¬it => can't continue match
|
||||
|
||||
let name_len = self.name_len;
|
||||
assert!(name_len > 0);
|
||||
let last_matched = self.name_buf()[name_len - 1..].chars().next().unwrap();
|
||||
|
||||
// There might not be a next character after the match, if
|
||||
// we had a full match and then hit EOF.
|
||||
let next_after = if name_len == self.name_buf().len() {
|
||||
None
|
||||
} else {
|
||||
Some(self.name_buf()[name_len..].chars().next().unwrap())
|
||||
};
|
||||
|
||||
// "If the character reference is being consumed as part of an
|
||||
// attribute, and the last character matched is not a U+003B
|
||||
// SEMICOLON character (;), and the next character is either a
|
||||
// U+003D EQUALS SIGN character (=) or an alphanumeric ASCII
|
||||
// character, then, for historical reasons, all the characters
|
||||
// that were matched after the U+0026 AMPERSAND character (&)
|
||||
// must be unconsumed, and nothing is returned. However, if
|
||||
// this next character is in fact a U+003D EQUALS SIGN
|
||||
// character (=), then this is a parse error"
|
||||
|
||||
let unconsume_all = match (self.addnl_allowed, last_matched, next_after) {
|
||||
(_, ';', _) => false,
|
||||
(Some(_), _, Some('=')) => {
|
||||
tokenizer.emit_error(Borrowed(
|
||||
"Equals sign after character reference in attribute",
|
||||
));
|
||||
true
|
||||
},
|
||||
(Some(_), _, Some(c)) if c.is_ascii_alphanumeric() => true,
|
||||
_ => {
|
||||
tokenizer.emit_error(Borrowed(
|
||||
"Character reference does not end with semicolon",
|
||||
));
|
||||
false
|
||||
},
|
||||
};
|
||||
|
||||
if unconsume_all {
|
||||
self.unconsume_name(tokenizer, input);
|
||||
self.finish_none()
|
||||
} else {
|
||||
tokenizer
|
||||
.unconsume(input, StrTendril::from_slice(&self.name_buf()[name_len..]));
|
||||
self.result = Some(CharRef {
|
||||
chars: [from_u32(c1).unwrap(), from_u32(c2).unwrap()],
|
||||
num_chars: if c2 == 0 { 1 } else { 2 },
|
||||
});
|
||||
Done
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn do_bogus_name<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) -> Status {
|
||||
let Some(c) = tokenizer.get_char(input) else {
|
||||
return Stuck;
|
||||
};
|
||||
self.name_buf_mut().push_char(c);
|
||||
match c {
|
||||
_ if c.is_ascii_alphanumeric() => return Progress,
|
||||
';' => self.emit_name_error(tokenizer),
|
||||
_ => (),
|
||||
}
|
||||
self.unconsume_name(tokenizer, input);
|
||||
self.finish_none()
|
||||
}
|
||||
|
||||
pub fn end_of_file<Sink: TokenSink>(
|
||||
&mut self,
|
||||
tokenizer: &XmlTokenizer<Sink>,
|
||||
input: &BufferQueue,
|
||||
) {
|
||||
while self.result.is_none() {
|
||||
match self.state {
|
||||
Begin => drop(self.finish_none()),
|
||||
|
||||
Numeric(_) if !self.seen_digit => drop(self.unconsume_numeric(tokenizer, input)),
|
||||
|
||||
Numeric(_) | NumericSemicolon => {
|
||||
tokenizer.emit_error(Borrowed("EOF in numeric character reference"));
|
||||
self.finish_numeric(tokenizer);
|
||||
},
|
||||
|
||||
Named => drop(self.finish_named(tokenizer, None, input)),
|
||||
|
||||
BogusName => {
|
||||
self.unconsume_name(tokenizer, input);
|
||||
self.finish_none();
|
||||
},
|
||||
|
||||
Octothorpe => {
|
||||
tokenizer.unconsume(input, StrTendril::from_slice("#"));
|
||||
tokenizer.emit_error(Borrowed("EOF after '#' in character reference"));
|
||||
self.finish_none();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::tendril::StrTendril;
|
||||
use crate::tokenizer::ProcessResult;
|
||||
use crate::{Attribute, QualName};
|
||||
|
||||
pub use self::TagKind::{EmptyTag, EndTag, ShortTag, StartTag};
|
||||
|
||||
/// Tag kind denotes which kind of tag did we encounter.
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||
pub enum TagKind {
|
||||
/// Beginning of a tag (e.g. `<a>`).
|
||||
StartTag,
|
||||
/// End of a tag (e.g. `</a>`).
|
||||
EndTag,
|
||||
/// Empty tag (e.g. `<a/>`).
|
||||
EmptyTag,
|
||||
/// Short tag (e.g. `</>`).
|
||||
ShortTag,
|
||||
}
|
||||
|
||||
/// XML 5 Tag Token
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct Tag {
|
||||
/// Token kind denotes which type of token was encountered.
|
||||
/// E.g. if parser parsed `</a>` the token kind would be `EndTag`.
|
||||
pub kind: TagKind,
|
||||
/// Qualified name of the tag.
|
||||
pub name: QualName,
|
||||
/// List of attributes attached to this tag.
|
||||
/// Only valid in start and empty tag.
|
||||
pub attrs: Vec<Attribute>,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
/// Sorts attributes in a tag.
|
||||
pub fn equiv_modulo_attr_order(&self, other: &Tag) -> bool {
|
||||
if (self.kind != other.kind) || (self.name != other.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut self_attrs = self.attrs.clone();
|
||||
let mut other_attrs = other.attrs.clone();
|
||||
self_attrs.sort();
|
||||
other_attrs.sort();
|
||||
|
||||
self_attrs == other_attrs
|
||||
}
|
||||
}
|
||||
|
||||
/// A `DOCTYPE` token.
|
||||
/// Doctype token in XML5 is rather limited for reasons, such as:
|
||||
/// security and simplicity. XML5 only supports declaring DTD with
|
||||
/// name, public identifier and system identifier
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Default)]
|
||||
pub struct Doctype {
|
||||
/// Name of DOCTYPE declared
|
||||
pub name: Option<StrTendril>,
|
||||
/// Public identifier of this DOCTYPE.
|
||||
pub public_id: Option<StrTendril>,
|
||||
/// System identifier of this DOCTYPE.
|
||||
pub system_id: Option<StrTendril>,
|
||||
}
|
||||
|
||||
/// A ProcessingInstruction token.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct Pi {
|
||||
/// What is the name of processing instruction.
|
||||
pub target: StrTendril,
|
||||
|
||||
/// Text of processing instruction.
|
||||
pub data: StrTendril,
|
||||
}
|
||||
|
||||
/// Describes tokens encountered during parsing of input.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum Token {
|
||||
/// Doctype token
|
||||
Doctype(Doctype),
|
||||
/// Token tag founds. This token applies to all
|
||||
/// possible kinds of tags (like start, end, empty tag, etc.).
|
||||
Tag(Tag),
|
||||
/// Processing Instruction token
|
||||
ProcessingInstruction(Pi),
|
||||
/// Comment token.
|
||||
Comment(StrTendril),
|
||||
/// Token that represents a series of characters.
|
||||
Characters(StrTendril),
|
||||
/// Token that represents a CDATA section.
|
||||
Cdata(StrTendril),
|
||||
/// End of File found.
|
||||
EndOfFile,
|
||||
/// NullCharacter encountered.
|
||||
NullCharacter,
|
||||
/// Error happened
|
||||
ParseError(Cow<'static, str>),
|
||||
}
|
||||
|
||||
/// Types which can receive tokens from the tokenizer.
|
||||
pub trait TokenSink {
|
||||
/// Handle to a DOM script element
|
||||
type Handle;
|
||||
|
||||
/// Process a token.
|
||||
fn process_token(&self, token: Token) -> ProcessResult<Self::Handle>;
|
||||
|
||||
/// Signal to the sink that parsing has ended.
|
||||
fn end(&self) {}
|
||||
}
|
||||
-1536
File diff suppressed because it is too large
Load Diff
-84
@@ -1,84 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
enum QualNameState {
|
||||
BeforeName,
|
||||
InName,
|
||||
AfterColon,
|
||||
}
|
||||
|
||||
pub struct QualNameTokenizer<'a> {
|
||||
state: QualNameState,
|
||||
slice: &'a [u8],
|
||||
valid_index: Option<u32>,
|
||||
curr_ind: usize,
|
||||
}
|
||||
|
||||
impl QualNameTokenizer<'_> {
|
||||
pub fn new(tag: &[u8]) -> QualNameTokenizer<'_> {
|
||||
QualNameTokenizer {
|
||||
state: QualNameState::BeforeName,
|
||||
slice: tag,
|
||||
valid_index: None,
|
||||
curr_ind: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Option<u32> {
|
||||
if !self.slice.is_empty() {
|
||||
loop {
|
||||
if !self.step() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.valid_index
|
||||
}
|
||||
|
||||
fn incr(&mut self) -> bool {
|
||||
if self.curr_ind + 1 < self.slice.len() {
|
||||
self.curr_ind += 1;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn step(&mut self) -> bool {
|
||||
match self.state {
|
||||
QualNameState::BeforeName => self.do_before_name(),
|
||||
QualNameState::InName => self.do_in_name(),
|
||||
QualNameState::AfterColon => self.do_after_colon(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_before_name(&mut self) -> bool {
|
||||
if self.slice[self.curr_ind] == b':' {
|
||||
false
|
||||
} else {
|
||||
self.state = QualNameState::InName;
|
||||
self.incr()
|
||||
}
|
||||
}
|
||||
|
||||
fn do_in_name(&mut self) -> bool {
|
||||
if self.slice[self.curr_ind] == b':' && self.curr_ind + 1 < self.slice.len() {
|
||||
self.valid_index = Some(self.curr_ind as u32);
|
||||
self.state = QualNameState::AfterColon;
|
||||
}
|
||||
self.incr()
|
||||
}
|
||||
|
||||
fn do_after_colon(&mut self) -> bool {
|
||||
if self.slice[self.curr_ind] == b':' {
|
||||
self.valid_index = None;
|
||||
return false;
|
||||
}
|
||||
self.incr()
|
||||
}
|
||||
}
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Tokenizer states.
|
||||
|
||||
/// Specifies either the public or system identifier from a [Document Type Declaration] (DTD).
|
||||
///
|
||||
/// [Document Type Declaration]: https://en.wikipedia.org/wiki/Document_type_declaration
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, Debug)]
|
||||
pub enum DoctypeKind {
|
||||
/// The public identifier.
|
||||
Public,
|
||||
/// The system identifier.
|
||||
System,
|
||||
}
|
||||
|
||||
/// Specifies the different states a XML tokenizer will assume during parsing.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, Debug)]
|
||||
pub enum XmlState {
|
||||
/// The initial state of the parser.
|
||||
///
|
||||
/// It is equivalent to the [`Data`](https://html.spec.whatwg.org/#data-state) state of the html parser,
|
||||
/// except null codepoints do not cause errors.
|
||||
Data,
|
||||
/// Indicates that the parser has found a `<` character and will try to parse a tag.
|
||||
TagState,
|
||||
/// Indicates that the parser has consumed the `/` of a closing tag, like `</foo>`.
|
||||
EndTagState,
|
||||
/// Indicates that the parser is currently parsing the name of a closing tag, like the `foo` of `</foo>`.
|
||||
EndTagName,
|
||||
/// Indicates that the parser has finished parsing the name of a closing tag and expects a `>` to follow.
|
||||
EndTagNameAfter,
|
||||
/// Indicates that the parser has started parsing a [processing instruction] (PI).
|
||||
///
|
||||
/// This state is reached after the initial `?` character has been consumed.
|
||||
///
|
||||
/// [processing instruction]: https://en.wikipedia.org/wiki/Processing_Instruction
|
||||
Pi,
|
||||
/// Indicates that the parser is currently parsing the target of a [processing instruction].
|
||||
///
|
||||
/// For example, the target of `<?xml-stylesheet type="text/xsl" href="style.xsl"?>` is `xml-stylesheet`.
|
||||
///
|
||||
/// [processing instruction]: https://en.wikipedia.org/wiki/Processing_Instruction
|
||||
PiTarget,
|
||||
/// Indicates that the parser has finished parsing the target of a [processing instruction].
|
||||
///
|
||||
/// [processing instruction]: https://en.wikipedia.org/wiki/Processing_Instruction
|
||||
PiTargetAfter,
|
||||
/// Indicates that the parser is currently parsing the data of a [processing instruction].
|
||||
///
|
||||
/// The "data" refers to everything between the target and the closing `?` character.
|
||||
///
|
||||
/// [processing instruction]: https://en.wikipedia.org/wiki/Processing_Instruction
|
||||
PiData,
|
||||
/// Indicates that the parser has parsed the closing `?` of a [processing instruction].
|
||||
///
|
||||
/// [processing instruction]: https://en.wikipedia.org/wiki/Processing_Instruction
|
||||
PiAfter,
|
||||
/// Indicates that the parser has parsed the initial `!` of a markup declaration.
|
||||
///
|
||||
/// Examples of such declarations include `<!ENTITY chap1 SYSTEM "chap1.xml">` or `<!-- Comment -->`.
|
||||
MarkupDecl,
|
||||
/// Indicates that the parser has parsed the start of a comment (`<!--`).
|
||||
CommentStart,
|
||||
/// Indicates that the parser has parsed the start of a comment and a `-` directly after it.
|
||||
CommentStartDash,
|
||||
/// Indicates that the parser is currently parsing the data within a comment.
|
||||
Comment,
|
||||
/// Indicates that the parser has parsed a `<` character within a comment.
|
||||
CommentLessThan,
|
||||
/// Indicates that the parser has parsed `<!` within a comment.
|
||||
CommentLessThanBang,
|
||||
/// Indicates that the parser has parsed `<!-` within a comment.
|
||||
CommentLessThanBangDash,
|
||||
/// Indicates that the parser has parsed `<!--` within a comment.
|
||||
CommentLessThanBangDashDash,
|
||||
/// Indicates that the parser has parsed two `-` characters within a comment which may or may not
|
||||
/// be the beginning of the comment end (`-->`).
|
||||
CommentEnd,
|
||||
/// Indicates that the parser has parsed a `-` character within a comment which may or may not
|
||||
/// be the beginning of the comment end (`-->`).
|
||||
CommentEndDash,
|
||||
/// Indicates that the parser has parsed `--!` within a comment which may or may not be part of the
|
||||
/// end of the comment. Comments in XML can be closed with `--!>`.
|
||||
CommentEndBang,
|
||||
/// Indicates that the parser has parsed the beginning of a CDATA section (`<![CDATA[`).
|
||||
Cdata,
|
||||
/// Indicates that the parser has parsed a `]` character within a CDATA section, which may be part of
|
||||
/// the end of the section (`]]>`).
|
||||
CdataBracket,
|
||||
/// Indicates that the parser has parsed two `]` characters within a CDATA section, which may be part of
|
||||
/// the end of the section (`]]>`).
|
||||
CdataEnd,
|
||||
/// Indicates that the parser is currently parsing the name of a tag, such as `foo` in `<foo>`.
|
||||
TagName,
|
||||
/// Indicates that the parser has parsed the `/` of a self-closing tag, such as `<foo/>`.
|
||||
TagEmpty,
|
||||
/// Indicates that the parser has finished parsing the name of a tag and is now expecting either attributes or
|
||||
/// a `>`.
|
||||
TagAttrNameBefore,
|
||||
/// Indicates that the parser is currently parsing the name of an attribute within a tag, such as
|
||||
/// `bar` in `<foo bar=baz>`.
|
||||
TagAttrName,
|
||||
/// Indicates that the parser has finished parsing the name of an attribute.
|
||||
TagAttrNameAfter,
|
||||
/// Indicates that the parser is about to parse the value of an attribute.
|
||||
TagAttrValueBefore,
|
||||
/// Indicates that the parser is currently parsing the value of an attribute, such as `baz` in
|
||||
/// `<foo bar=baz>`.
|
||||
///
|
||||
/// Includes information about how the value is quoted, because the quotes before and after the attribute
|
||||
/// value need to match.
|
||||
TagAttrValue(AttrValueKind),
|
||||
/// Indicates that the parser has parsed the beginning of a document type definition (`<!DOCTYPE`).
|
||||
Doctype,
|
||||
/// Indicates that the parser expects to parse the name of the document type definition next.
|
||||
BeforeDoctypeName,
|
||||
/// Indicates that the parser is currently parsing the name of a document type definition, such as
|
||||
/// `html` in `<!DOCTYPE html>`.
|
||||
DoctypeName,
|
||||
/// Indicates that the parser has finished parsing the name of the document type definition and now optionally
|
||||
/// expects either a public or a system identifier.
|
||||
AfterDoctypeName,
|
||||
/// Indicates that the parser has parsed a keyword for either a public or system identifier (`PUBLIC` or `SYSTEM`).
|
||||
AfterDoctypeKeyword(DoctypeKind),
|
||||
/// Indicates that the parser is about to parse the value of a public or system identifier within
|
||||
/// a document type definition, such as `foo` in
|
||||
/// `<!DOCTYPE html PUBLIC "foo" "bar">`.
|
||||
BeforeDoctypeIdentifier(DoctypeKind),
|
||||
/// Indicates that the parser is currently parsing the value of a public or system identifier
|
||||
/// that is surrounded by double quotes , such as `foo` in
|
||||
/// `<!DOCTYPE html PUBLIC "foo" "bar">`.
|
||||
DoctypeIdentifierDoubleQuoted(DoctypeKind),
|
||||
/// Indicates that the parser is currently parsing the value of a public or system identifier
|
||||
/// that is surrounded by single quotes , such as `foo` in
|
||||
/// `<!DOCTYPE html PUBLIC 'foo' 'bar'>`.
|
||||
DoctypeIdentifierSingleQuoted(DoctypeKind),
|
||||
/// Indicates that the parser has finished parsing either a public or system identifier within a
|
||||
/// document type definition.
|
||||
AfterDoctypeIdentifier(DoctypeKind),
|
||||
/// Indicates that the parser has finished parsing a public identifier and now expects
|
||||
/// a system identifier.
|
||||
BetweenDoctypePublicAndSystemIdentifiers,
|
||||
/// Indicates that the parser is currently parsing an ill-formed document type defintion, such as
|
||||
/// `<!DOCTYPE html what-is-this>`.
|
||||
BogusDoctype,
|
||||
/// Indicates that the parser is currently parsing an ill-formed comment, such as
|
||||
/// `<? this is not what a comment should look like! >`.
|
||||
BogusComment,
|
||||
}
|
||||
|
||||
/// Specifies how an attribute value is quoted, if at all.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash, Debug)]
|
||||
pub enum AttrValueKind {
|
||||
/// A attribute value that is not surrounded by quotes, like `bar` in `foo=bar`.
|
||||
Unquoted,
|
||||
/// A attribute value that is not surrounded by quotes, like `bar` in `foo='bar'`.
|
||||
SingleQuoted,
|
||||
/// A attribute value that is not surrounded by quotes, like `bar` in `foo="bar"`.
|
||||
DoubleQuoted,
|
||||
}
|
||||
-800
@@ -1,800 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
mod types;
|
||||
|
||||
use log::{debug, warn};
|
||||
use markup5ever::{local_name, namespace_prefix, ns};
|
||||
use std::borrow::Cow;
|
||||
use std::borrow::Cow::Borrowed;
|
||||
use std::cell::{Cell, Ref, RefCell};
|
||||
use std::collections::btree_map::Iter;
|
||||
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||
use std::fmt::{Debug, Error, Formatter};
|
||||
use std::mem;
|
||||
|
||||
pub use self::interface::{ElemName, NodeOrText, Tracer, TreeSink};
|
||||
use self::types::*;
|
||||
use crate::interface::{self, create_element, AppendNode, Attribute, QualName};
|
||||
use crate::interface::{AppendText, ExpandedName};
|
||||
use crate::tokenizer::{self, EndTag, ProcessResult, StartTag, Tag, TokenSink};
|
||||
use crate::tokenizer::{Doctype, EmptyTag, Pi, ShortTag};
|
||||
use crate::{LocalName, Namespace, Prefix};
|
||||
|
||||
use crate::tendril::{StrTendril, Tendril};
|
||||
|
||||
/// XML-specific tree sink operations that are not part of markup5ever's
|
||||
/// HTML-oriented [`TreeSink`] interface.
|
||||
pub trait XmlTreeSink: TreeSink {
|
||||
/// Create a CDATA section node.
|
||||
fn create_cdata(&self, text: StrTendril) -> Self::Handle;
|
||||
}
|
||||
|
||||
static XML_URI: &str = "http://www.w3.org/XML/1998/namespace";
|
||||
static XMLNS_URI: &str = "http://www.w3.org/2000/xmlns/";
|
||||
|
||||
type InsResult = Result<(), Cow<'static, str>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NamespaceMapStack(Vec<NamespaceMap>);
|
||||
|
||||
impl NamespaceMapStack {
|
||||
fn new() -> NamespaceMapStack {
|
||||
NamespaceMapStack(vec![NamespaceMap::default()])
|
||||
}
|
||||
|
||||
fn push(&mut self, map: NamespaceMap) {
|
||||
self.0.push(map);
|
||||
}
|
||||
|
||||
fn pop(&mut self) {
|
||||
self.0.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct NamespaceMap {
|
||||
// Map that maps prefixes to URI.
|
||||
//
|
||||
// Key denotes namespace prefix, and value denotes
|
||||
// URI it maps to.
|
||||
//
|
||||
// If value of value is None, that means the namespace
|
||||
// denoted by key has been undeclared.
|
||||
scope: BTreeMap<Option<Prefix>, Option<Namespace>>,
|
||||
}
|
||||
|
||||
impl Debug for NamespaceMap {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
|
||||
write!(f, "\nNamespaceMap[")?;
|
||||
for (key, value) in &self.scope {
|
||||
writeln!(f, " {key:?} : {value:?}")?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
impl NamespaceMap {
|
||||
// Returns an empty namespace.
|
||||
pub(crate) fn empty() -> NamespaceMap {
|
||||
NamespaceMap {
|
||||
scope: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn default() -> NamespaceMap {
|
||||
NamespaceMap {
|
||||
scope: {
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert(None, None);
|
||||
map.insert(Some(namespace_prefix!("xml")), Some(ns!(xml)));
|
||||
map.insert(Some(namespace_prefix!("xmlns")), Some(ns!(xmlns)));
|
||||
map
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get(&self, prefix: &Option<Prefix>) -> Option<&Option<Namespace>> {
|
||||
self.scope.get(prefix)
|
||||
}
|
||||
|
||||
pub(crate) fn get_scope_iter(&self) -> Iter<'_, Option<Prefix>, Option<Namespace>> {
|
||||
self.scope.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, name: &QualName) {
|
||||
let prefix = name.prefix.as_ref().cloned();
|
||||
let namespace = Some(Namespace::from(&*name.ns));
|
||||
self.scope.insert(prefix, namespace);
|
||||
}
|
||||
|
||||
fn insert_ns(&mut self, attr: &Attribute) -> InsResult {
|
||||
if &*attr.value == XMLNS_URI {
|
||||
return Err(Borrowed("Can't declare XMLNS URI"));
|
||||
};
|
||||
|
||||
let opt_uri = if attr.value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Namespace::from(&*attr.value))
|
||||
};
|
||||
|
||||
let result = match (&attr.name.prefix, &*attr.name.local) {
|
||||
(&Some(namespace_prefix!("xmlns")), "xml") => {
|
||||
if &*attr.value != XML_URI {
|
||||
Err(Borrowed("XML namespace can't be redeclared"))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|
||||
(&Some(namespace_prefix!("xmlns")), "xmlns") => {
|
||||
Err(Borrowed("XMLNS namespaces can't be changed"))
|
||||
},
|
||||
|
||||
(&Some(namespace_prefix!("xmlns")), _) | (&None, "xmlns") => {
|
||||
// We can have two cases of properly defined xmlns
|
||||
// First with default namespace e.g.
|
||||
//
|
||||
// <a xmlns = "www.uri.org" />
|
||||
let ns_prefix = if &*attr.name.local == "xmlns" {
|
||||
None
|
||||
|
||||
// Second is with named namespace e.g.
|
||||
//
|
||||
// <a xmlns:a = "www.uri.org" />
|
||||
} else {
|
||||
Some(Prefix::from(&*attr.name.local))
|
||||
};
|
||||
|
||||
if opt_uri.is_some() && self.scope.contains_key(&ns_prefix) {
|
||||
Err(Borrowed("Namespace already defined"))
|
||||
} else {
|
||||
self.scope.insert(ns_prefix, opt_uri);
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
|
||||
(_, _) => Err(Borrowed("Invalid namespace declaration.")),
|
||||
};
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Tree builder options, with an impl for Default.
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct XmlTreeBuilderOpts {}
|
||||
|
||||
/// The XML tree builder.
|
||||
pub struct XmlTreeBuilder<Handle, Sink> {
|
||||
/// Configuration options for XmlTreeBuilder
|
||||
_opts: XmlTreeBuilderOpts,
|
||||
|
||||
/// Consumer of tree modifications.
|
||||
pub sink: Sink,
|
||||
|
||||
/// The document node, which is created by the sink.
|
||||
doc_handle: Handle,
|
||||
|
||||
/// Stack of open elements, most recently added at end.
|
||||
open_elems: RefCell<Vec<Handle>>,
|
||||
|
||||
/// Current element pointer.
|
||||
curr_elem: RefCell<Option<Handle>>,
|
||||
|
||||
/// Stack of namespace identifiers and namespaces.
|
||||
namespace_stack: RefCell<NamespaceMapStack>,
|
||||
|
||||
/// Current namespace identifier
|
||||
current_namespace: RefCell<NamespaceMap>,
|
||||
|
||||
/// Current tree builder phase.
|
||||
phase: Cell<XmlPhase>,
|
||||
}
|
||||
impl<Handle, Sink> XmlTreeBuilder<Handle, Sink>
|
||||
where
|
||||
Handle: Clone,
|
||||
Sink: XmlTreeSink<Handle = Handle>,
|
||||
{
|
||||
/// Create a new tree builder which sends tree modifications to a particular `TreeSink`.
|
||||
///
|
||||
/// The tree builder is also a `TokenSink`.
|
||||
pub fn new(sink: Sink, opts: XmlTreeBuilderOpts) -> XmlTreeBuilder<Handle, Sink> {
|
||||
let doc_handle = sink.get_document();
|
||||
XmlTreeBuilder {
|
||||
_opts: opts,
|
||||
sink,
|
||||
doc_handle,
|
||||
open_elems: RefCell::new(vec![]),
|
||||
curr_elem: RefCell::new(None),
|
||||
namespace_stack: RefCell::new(NamespaceMapStack::new()),
|
||||
current_namespace: RefCell::new(NamespaceMap::empty()),
|
||||
phase: Cell::new(XmlPhase::Start),
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the `Tracer`'s `trace_handle` method on every `Handle` in the tree builder's
|
||||
/// internal state. This is intended to support garbage-collected DOMs.
|
||||
pub fn trace_handles(&self, tracer: &dyn Tracer<Handle = Handle>) {
|
||||
tracer.trace_handle(&self.doc_handle);
|
||||
for e in self.open_elems.borrow().iter() {
|
||||
tracer.trace_handle(e);
|
||||
}
|
||||
if let Some(h) = self.curr_elem.borrow().as_ref() {
|
||||
tracer.trace_handle(h);
|
||||
}
|
||||
}
|
||||
|
||||
// Debug helper
|
||||
#[cfg(not(for_c))]
|
||||
#[allow(dead_code)]
|
||||
fn dump_state(&self, label: String) {
|
||||
debug!("dump_state on {label}");
|
||||
debug!(" open_elems:");
|
||||
for node in self.open_elems.borrow().iter() {
|
||||
debug!(" {:?}", self.sink.elem_name(node));
|
||||
}
|
||||
debug!("");
|
||||
}
|
||||
|
||||
#[cfg(for_c)]
|
||||
fn debug_step(&self, _mode: XmlPhase, _token: &Token) {}
|
||||
|
||||
#[cfg(not(for_c))]
|
||||
fn debug_step(&self, mode: XmlPhase, token: &Token) {
|
||||
debug!(
|
||||
"processing {:?} in insertion mode {:?}",
|
||||
format!("{:?}", token),
|
||||
mode
|
||||
);
|
||||
}
|
||||
|
||||
fn declare_ns(&self, attr: &mut Attribute) {
|
||||
if let Err(msg) = self.current_namespace.borrow_mut().insert_ns(attr) {
|
||||
self.sink.parse_error(msg);
|
||||
}
|
||||
// Namespace declarations are real DOM attributes. Keep their XMLNS
|
||||
// namespace identity even when the declaration itself is invalid.
|
||||
attr.name.ns = ns!(xmlns);
|
||||
}
|
||||
|
||||
fn find_uri(&self, prefix: &Option<Prefix>) -> Result<Option<Namespace>, Cow<'static, str>> {
|
||||
let mut uri = Err(Borrowed("No appropriate namespace found"));
|
||||
|
||||
let current_namespace = self.current_namespace.borrow();
|
||||
for ns in self
|
||||
.namespace_stack
|
||||
.borrow()
|
||||
.0
|
||||
.iter()
|
||||
.chain(Some(&*current_namespace))
|
||||
.rev()
|
||||
{
|
||||
if let Some(el) = ns.get(prefix) {
|
||||
uri = Ok(el.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
uri
|
||||
}
|
||||
|
||||
fn bind_qname(&self, name: &mut QualName) {
|
||||
match self.find_uri(&name.prefix) {
|
||||
Ok(uri) => {
|
||||
let ns_uri = match uri {
|
||||
Some(e) => e,
|
||||
None => ns!(),
|
||||
};
|
||||
name.ns = ns_uri;
|
||||
},
|
||||
Err(msg) => {
|
||||
self.sink.parse_error(msg);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// This method takes in name qualified name and binds it to the
|
||||
// existing namespace context.
|
||||
//
|
||||
// Returns false if the attribute is a duplicate, returns true otherwise.
|
||||
fn bind_attr_qname(
|
||||
&self,
|
||||
present_attrs: &mut HashSet<(Namespace, LocalName)>,
|
||||
name: &mut QualName,
|
||||
) -> bool {
|
||||
// Attributes don't have default namespace
|
||||
let mut not_duplicate = true;
|
||||
|
||||
if name.prefix.is_some() {
|
||||
self.bind_qname(name);
|
||||
not_duplicate = Self::check_duplicate_attr(present_attrs, name);
|
||||
}
|
||||
not_duplicate
|
||||
}
|
||||
|
||||
fn check_duplicate_attr(
|
||||
present_attrs: &mut HashSet<(Namespace, LocalName)>,
|
||||
name: &QualName,
|
||||
) -> bool {
|
||||
let pair = (name.ns.clone(), name.local.clone());
|
||||
|
||||
if present_attrs.contains(&pair) {
|
||||
return false;
|
||||
}
|
||||
present_attrs.insert(pair);
|
||||
true
|
||||
}
|
||||
|
||||
fn process_namespaces(&self, tag: &mut Tag) {
|
||||
// List of already present namespace local name attribute pairs.
|
||||
let mut present_attrs: HashSet<(Namespace, LocalName)> = Default::default();
|
||||
|
||||
let mut new_attr = vec![];
|
||||
// First we extract all namespace declarations
|
||||
for attr in tag.attrs.iter_mut().filter(|attr| {
|
||||
attr.name.prefix == Some(namespace_prefix!("xmlns"))
|
||||
|| attr.name.local == local_name!("xmlns")
|
||||
}) {
|
||||
self.declare_ns(attr);
|
||||
}
|
||||
|
||||
// Bind ordinary attributes and retain namespace declarations in their
|
||||
// original order. Tree sinks need the declarations as Attr nodes even
|
||||
// though the namespace resolver has already consumed their values.
|
||||
for attr in tag.attrs.iter_mut() {
|
||||
let is_namespace_declaration = attr.name.ns == ns!(xmlns);
|
||||
let keep = if is_namespace_declaration {
|
||||
Self::check_duplicate_attr(&mut present_attrs, &attr.name)
|
||||
} else {
|
||||
self.bind_attr_qname(&mut present_attrs, &mut attr.name)
|
||||
};
|
||||
if keep {
|
||||
new_attr.push(attr.clone());
|
||||
}
|
||||
}
|
||||
tag.attrs = new_attr;
|
||||
|
||||
// Then we bind the tags namespace.
|
||||
self.bind_qname(&mut tag.name);
|
||||
|
||||
// Finally, we dump current namespace if its unneeded.
|
||||
let x = mem::replace(
|
||||
&mut *self.current_namespace.borrow_mut(),
|
||||
NamespaceMap::empty(),
|
||||
);
|
||||
|
||||
// Only start tag doesn't dump current namespace. However, <script /> is treated
|
||||
// differently than every other empty tag, so it needs to retain the current
|
||||
// namespace as well.
|
||||
if tag.kind == StartTag || (tag.kind == EmptyTag && tag.name.local == local_name!("script"))
|
||||
{
|
||||
self.namespace_stack.borrow_mut().push(x);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_to_completion(
|
||||
&self,
|
||||
mut token: Token,
|
||||
) -> ProcessResult<<Self as TokenSink>::Handle> {
|
||||
// Queue of additional tokens yet to be processed.
|
||||
// This stays empty in the common case where we don't split whitespace.
|
||||
let mut more_tokens = VecDeque::new();
|
||||
|
||||
loop {
|
||||
let phase = self.phase.get();
|
||||
|
||||
#[allow(clippy::unused_unit)]
|
||||
match self.step(phase, token) {
|
||||
XmlProcessResult::Done => {
|
||||
let Some(popped_token) = more_tokens.pop_front() else {
|
||||
return ProcessResult::Continue;
|
||||
};
|
||||
token = popped_token;
|
||||
},
|
||||
XmlProcessResult::Reprocess(m, t) => {
|
||||
self.phase.set(m);
|
||||
token = t;
|
||||
},
|
||||
XmlProcessResult::Script(node) => {
|
||||
assert!(more_tokens.is_empty());
|
||||
return ProcessResult::Script(node);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Handle, Sink> TokenSink for XmlTreeBuilder<Handle, Sink>
|
||||
where
|
||||
Handle: Clone,
|
||||
Sink: XmlTreeSink<Handle = Handle>,
|
||||
{
|
||||
type Handle = Handle;
|
||||
|
||||
fn process_token(&self, token: tokenizer::Token) -> ProcessResult<Self::Handle> {
|
||||
// Handle `ParseError` and `DoctypeToken`; convert everything else to the local `Token` type.
|
||||
let token = match token {
|
||||
tokenizer::Token::ParseError(e) => {
|
||||
self.sink.parse_error(e);
|
||||
return ProcessResult::Done;
|
||||
},
|
||||
|
||||
tokenizer::Token::Doctype(d) => Token::Doctype(d),
|
||||
tokenizer::Token::ProcessingInstruction(instruction) => Token::Pi(instruction),
|
||||
tokenizer::Token::Tag(x) => Token::Tag(x),
|
||||
tokenizer::Token::Comment(x) => Token::Comment(x),
|
||||
tokenizer::Token::NullCharacter => Token::NullCharacter,
|
||||
tokenizer::Token::EndOfFile => Token::Eof,
|
||||
tokenizer::Token::Characters(x) => Token::Characters(x),
|
||||
tokenizer::Token::Cdata(x) => Token::Cdata(x),
|
||||
};
|
||||
|
||||
self.process_to_completion(token)
|
||||
}
|
||||
|
||||
fn end(&self) {
|
||||
let mut open_elems = self.open_elems.borrow_mut();
|
||||
if !open_elems.is_empty() {
|
||||
self.sink
|
||||
.parse_error(Borrowed("Unexpected EOF with an unclosed XML element"));
|
||||
}
|
||||
for node in open_elems.drain(..).rev() {
|
||||
self.sink.pop(&node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn current_node<Handle>(open_elems: &[Handle]) -> &Handle {
|
||||
open_elems.last().expect("no current element")
|
||||
}
|
||||
|
||||
impl<Handle, Sink> XmlTreeBuilder<Handle, Sink>
|
||||
where
|
||||
Handle: Clone,
|
||||
Sink: XmlTreeSink<Handle = Handle>,
|
||||
{
|
||||
fn current_node(&self) -> Ref<'_, Handle> {
|
||||
Ref::map(self.open_elems.borrow(), |elems| {
|
||||
elems.last().expect("no current element")
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_appropriately(&self, child: NodeOrText<Handle>) {
|
||||
let open_elems = self.open_elems.borrow();
|
||||
let target = current_node(&open_elems);
|
||||
self.sink.append(target, child);
|
||||
}
|
||||
|
||||
fn insert_tag(&self, tag: Tag) -> XmlProcessResult<Handle> {
|
||||
let child = create_element(&self.sink, tag.name, tag.attrs);
|
||||
self.insert_appropriately(AppendNode(child.clone()));
|
||||
self.add_to_open_elems(child)
|
||||
}
|
||||
|
||||
fn append_tag(&self, tag: Tag) -> XmlProcessResult<Handle> {
|
||||
let child = create_element(&self.sink, tag.name, tag.attrs);
|
||||
self.insert_appropriately(AppendNode(child.clone()));
|
||||
self.sink.pop(&child);
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_tag_to_doc(&self, tag: Tag) -> Handle {
|
||||
let child = create_element(&self.sink, tag.name, tag.attrs);
|
||||
|
||||
self.sink
|
||||
.append(&self.doc_handle, AppendNode(child.clone()));
|
||||
child
|
||||
}
|
||||
|
||||
fn add_to_open_elems(&self, el: Handle) -> XmlProcessResult<Handle> {
|
||||
self.open_elems.borrow_mut().push(el);
|
||||
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_comment_to_doc(&self, text: StrTendril) -> XmlProcessResult<Handle> {
|
||||
let comment = self.sink.create_comment(text);
|
||||
self.sink.append(&self.doc_handle, AppendNode(comment));
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_comment_to_tag(&self, text: StrTendril) -> XmlProcessResult<Handle> {
|
||||
let open_elems = self.open_elems.borrow();
|
||||
let target = current_node(&open_elems);
|
||||
let comment = self.sink.create_comment(text);
|
||||
self.sink.append(target, AppendNode(comment));
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_doctype_to_doc(&self, doctype: Doctype) -> XmlProcessResult<Handle> {
|
||||
fn get_tendril(opt: Option<StrTendril>) -> StrTendril {
|
||||
match opt {
|
||||
Some(expr) => expr,
|
||||
None => Tendril::new(),
|
||||
}
|
||||
}
|
||||
self.sink.append_doctype_to_document(
|
||||
get_tendril(doctype.name),
|
||||
get_tendril(doctype.public_id),
|
||||
get_tendril(doctype.system_id),
|
||||
);
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_pi_to_doc(&self, pi: Pi) -> XmlProcessResult<Handle> {
|
||||
let pi = self.sink.create_pi(pi.target, pi.data);
|
||||
self.sink.append(&self.doc_handle, AppendNode(pi));
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_pi_to_tag(&self, pi: Pi) -> XmlProcessResult<Handle> {
|
||||
let open_elems = self.open_elems.borrow();
|
||||
let target = current_node(&open_elems);
|
||||
let pi = self.sink.create_pi(pi.target, pi.data);
|
||||
self.sink.append(target, AppendNode(pi));
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_text(&self, chars: StrTendril) -> XmlProcessResult<Handle> {
|
||||
self.insert_appropriately(AppendText(chars));
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn append_cdata(&self, text: StrTendril) -> XmlProcessResult<Handle> {
|
||||
let cdata = self.sink.create_cdata(text);
|
||||
self.insert_appropriately(AppendNode(cdata));
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn tag_in_open_elems(&self, tag: &Tag) -> bool {
|
||||
self.open_elems
|
||||
.borrow()
|
||||
.iter()
|
||||
.any(|a| self.sink.elem_name(a).expanded() == tag.name.expanded())
|
||||
}
|
||||
|
||||
// Pop elements until an element from the set has been popped.
|
||||
fn pop_until<P>(&self, pred: P)
|
||||
where
|
||||
P: Fn(ExpandedName) -> bool,
|
||||
{
|
||||
loop {
|
||||
if self.current_node_in(&pred) {
|
||||
break;
|
||||
}
|
||||
self.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fn current_node_in<TagSet>(&self, set: TagSet) -> bool
|
||||
where
|
||||
TagSet: Fn(ExpandedName) -> bool,
|
||||
{
|
||||
// FIXME: take namespace into consideration:
|
||||
set(self.sink.elem_name(&self.current_node()).expanded())
|
||||
}
|
||||
|
||||
fn close_tag(&self, tag: Tag) -> XmlProcessResult<Handle> {
|
||||
debug!(
|
||||
"Close tag: current_node.name {:?} \n Current tag {:?}",
|
||||
self.sink.elem_name(&self.current_node()),
|
||||
tag.name
|
||||
);
|
||||
|
||||
if *self.sink.elem_name(&self.current_node()).local_name() != tag.name.local {
|
||||
self.sink
|
||||
.parse_error(Borrowed("Current node doesn't match tag"));
|
||||
}
|
||||
|
||||
let is_closed = self.tag_in_open_elems(&tag);
|
||||
|
||||
if is_closed {
|
||||
self.pop_until(|p| p == tag.name.expanded());
|
||||
self.pop();
|
||||
}
|
||||
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
|
||||
fn no_open_elems(&self) -> bool {
|
||||
self.open_elems.borrow().is_empty()
|
||||
}
|
||||
|
||||
fn pop(&self) -> Handle {
|
||||
self.namespace_stack.borrow_mut().pop();
|
||||
let node = self
|
||||
.open_elems
|
||||
.borrow_mut()
|
||||
.pop()
|
||||
.expect("no current element");
|
||||
self.sink.pop(&node);
|
||||
node
|
||||
}
|
||||
|
||||
fn stop_parsing(&self) -> XmlProcessResult<Handle> {
|
||||
warn!("stop_parsing for XML5 not implemented, full speed ahead!");
|
||||
XmlProcessResult::Done
|
||||
}
|
||||
}
|
||||
|
||||
fn any_not_whitespace(x: &StrTendril) -> bool {
|
||||
!x.bytes()
|
||||
.all(|b| matches!(b, b'\t' | b'\r' | b'\n' | b'\x0C' | b' '))
|
||||
}
|
||||
|
||||
impl<Handle, Sink> XmlTreeBuilder<Handle, Sink>
|
||||
where
|
||||
Handle: Clone,
|
||||
Sink: XmlTreeSink<Handle = Handle>,
|
||||
{
|
||||
fn step(&self, mode: XmlPhase, token: Token) -> XmlProcessResult<<Self as TokenSink>::Handle> {
|
||||
self.debug_step(mode, &token);
|
||||
|
||||
match mode {
|
||||
XmlPhase::Start => match token {
|
||||
Token::Tag(Tag {
|
||||
kind: StartTag,
|
||||
name,
|
||||
attrs,
|
||||
}) => {
|
||||
let tag = {
|
||||
let mut tag = Tag {
|
||||
kind: StartTag,
|
||||
name,
|
||||
attrs,
|
||||
};
|
||||
self.process_namespaces(&mut tag);
|
||||
tag
|
||||
};
|
||||
self.phase.set(XmlPhase::Main);
|
||||
let handle = self.append_tag_to_doc(tag);
|
||||
self.add_to_open_elems(handle)
|
||||
},
|
||||
Token::Tag(Tag {
|
||||
kind: EmptyTag,
|
||||
name,
|
||||
attrs,
|
||||
}) => {
|
||||
let tag = {
|
||||
let mut tag = Tag {
|
||||
kind: EmptyTag,
|
||||
name,
|
||||
attrs,
|
||||
};
|
||||
self.process_namespaces(&mut tag);
|
||||
tag
|
||||
};
|
||||
self.phase.set(XmlPhase::End);
|
||||
let handle = self.append_tag_to_doc(tag);
|
||||
self.sink.pop(&handle);
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
Token::Comment(comment) => self.append_comment_to_doc(comment),
|
||||
Token::Pi(pi) => self.append_pi_to_doc(pi),
|
||||
Token::Characters(ref chars) if !any_not_whitespace(chars) => {
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
Token::Eof => {
|
||||
self.sink
|
||||
.parse_error(Borrowed("Unexpected EOF in start phase"));
|
||||
XmlProcessResult::Reprocess(XmlPhase::End, Token::Eof)
|
||||
},
|
||||
Token::Doctype(d) => {
|
||||
self.append_doctype_to_doc(d);
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
_ => {
|
||||
self.sink
|
||||
.parse_error(Borrowed("Unexpected element in start phase"));
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
},
|
||||
XmlPhase::Main => match token {
|
||||
Token::Characters(chs) => self.append_text(chs),
|
||||
Token::Cdata(text) => self.append_cdata(text),
|
||||
Token::Tag(Tag {
|
||||
kind: StartTag,
|
||||
name,
|
||||
attrs,
|
||||
}) => {
|
||||
let tag = {
|
||||
let mut tag = Tag {
|
||||
kind: StartTag,
|
||||
name,
|
||||
attrs,
|
||||
};
|
||||
self.process_namespaces(&mut tag);
|
||||
tag
|
||||
};
|
||||
self.insert_tag(tag)
|
||||
},
|
||||
Token::Tag(Tag {
|
||||
kind: EmptyTag,
|
||||
name,
|
||||
attrs,
|
||||
}) => {
|
||||
let tag = {
|
||||
let mut tag = Tag {
|
||||
kind: EmptyTag,
|
||||
name,
|
||||
attrs,
|
||||
};
|
||||
self.process_namespaces(&mut tag);
|
||||
tag
|
||||
};
|
||||
if tag.name.local == local_name!("script") {
|
||||
self.insert_tag(tag.clone());
|
||||
let script = current_node(&self.open_elems.borrow()).clone();
|
||||
self.close_tag(tag);
|
||||
XmlProcessResult::Script(script)
|
||||
} else {
|
||||
self.append_tag(tag)
|
||||
}
|
||||
},
|
||||
Token::Tag(Tag {
|
||||
kind: EndTag,
|
||||
name,
|
||||
attrs,
|
||||
}) => {
|
||||
let tag = {
|
||||
let mut tag = Tag {
|
||||
kind: EndTag,
|
||||
name,
|
||||
attrs,
|
||||
};
|
||||
self.process_namespaces(&mut tag);
|
||||
tag
|
||||
};
|
||||
if tag.name.local == local_name!("script") {
|
||||
let script = current_node(&self.open_elems.borrow()).clone();
|
||||
self.close_tag(tag);
|
||||
if self.no_open_elems() {
|
||||
self.phase.set(XmlPhase::End);
|
||||
}
|
||||
return XmlProcessResult::Script(script);
|
||||
}
|
||||
let retval = self.close_tag(tag);
|
||||
if self.no_open_elems() {
|
||||
self.phase.set(XmlPhase::End);
|
||||
}
|
||||
retval
|
||||
},
|
||||
Token::Tag(Tag { kind: ShortTag, .. }) => {
|
||||
self.pop();
|
||||
if self.no_open_elems() {
|
||||
self.phase.set(XmlPhase::End);
|
||||
}
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
Token::Comment(comment) => self.append_comment_to_tag(comment),
|
||||
Token::Pi(pi) => self.append_pi_to_tag(pi),
|
||||
Token::Eof | Token::NullCharacter => {
|
||||
XmlProcessResult::Reprocess(XmlPhase::End, Token::Eof)
|
||||
},
|
||||
Token::Doctype(_) => {
|
||||
self.sink
|
||||
.parse_error(Borrowed("Unexpected element in main phase"));
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
},
|
||||
XmlPhase::End => match token {
|
||||
Token::Comment(comment) => self.append_comment_to_doc(comment),
|
||||
Token::Pi(pi) => self.append_pi_to_doc(pi),
|
||||
Token::Characters(ref chars) if !any_not_whitespace(chars) => {
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
Token::Eof => self.stop_parsing(),
|
||||
_ => {
|
||||
self.sink
|
||||
.parse_error(Borrowed("Unexpected element in end phase"));
|
||||
XmlProcessResult::Done
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2014-2017 The html5ever Project Developers. See the
|
||||
// COPYRIGHT file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::tendril::StrTendril;
|
||||
use crate::tokenizer::{Doctype, Pi, Tag};
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum XmlPhase {
|
||||
Start,
|
||||
Main,
|
||||
End,
|
||||
}
|
||||
|
||||
/// A subset/refinement of `tokenizer::Token`. Everything else is handled
|
||||
/// specially at the beginning of `process_token`.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum Token {
|
||||
Tag(Tag),
|
||||
Doctype(Doctype),
|
||||
Comment(StrTendril),
|
||||
Characters(StrTendril),
|
||||
Cdata(StrTendril),
|
||||
Pi(Pi),
|
||||
NullCharacter,
|
||||
Eof,
|
||||
}
|
||||
|
||||
pub enum XmlProcessResult<Handle> {
|
||||
Done,
|
||||
Reprocess(XmlPhase, Token),
|
||||
Script(Handle),
|
||||
}
|
||||
Reference in New Issue
Block a user