diff --git a/.gitignore b/.gitignore index c7a968a96..d8f1c953c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,4 @@ local/ settings.db # C API tests (ignore everything but source files) -*/gosub_bindings/tests/* -!*/gosub_bindings/tests/*.c *.dSYM diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f32aa34b2..5f11722df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,10 +38,6 @@ In the root directory, run `make format` to have clippy automatically fix some o ### Running Benchmarks In the root directory, run `make bench` to run the benchmarks. -### Building C API Bindings -In the `crates/gosub_bindings` directory, run `make bindings` to build the C static libraries. For more information on this, see the [README](crates/gosub-bindings/README.md). - -Can also run `make test` in the same directory to build and run the tests for the C bindings. ## Code Style We use cargo's built-in formatter. When running `make test`, the formatter will also run (after all the tests) and display any issues in a `git diff`-like output in the terminal. Please resolve these formatting issues prior to pushing any code! This is validated in our CI when creating a pull request. diff --git a/Cargo.lock b/Cargo.lock index ef084b42f..0d565f97b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,17 +74,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.11" @@ -95,7 +84,7 @@ dependencies = [ "getrandom", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -107,66 +96,18 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "aligned-vec" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "allocator-api2" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" -[[package]] -name = "allsorts" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb67debdbc7e8b0716e7b10ce247ff6cd2811b0b478969158b16ac58995d16b" -dependencies = [ - "bitflags 1.3.2", - "bitreader", - "brotli-decompressor", - "byteorder", - "encoding_rs", - "flate2", - "glyph-names", - "itertools 0.10.5", - "lazy_static", - "libc", - "log", - "num-traits", - "ouroboros", - "rustc-hash", - "tinyvec", - "ucd-trie", - "unicode-canonical-combining-class", - "unicode-general-category", - "unicode-joining-type", -] - [[package]] name = "android-activity" version = "0.6.0" @@ -278,14 +219,14 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -295,9 +236,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "as-raw-xcb-connection" @@ -316,13 +257,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -333,9 +274,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" @@ -344,7 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" dependencies = [ "anyhow", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "log", "nom 7.1.3", "num-rational", @@ -357,7 +298,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", ] [[package]] @@ -400,7 +341,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.77", + "syn 2.0.79", "which 4.4.2", ] @@ -452,20 +393,11 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -[[package]] -name = "bitreader" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd859c9d97f7c468252795b35aeccc412bdbb1e90ee6969c4fa6328272eaeff" -dependencies = [ - "cfg-if", -] - [[package]] name = "bitstream-io" -version = "2.5.0" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" [[package]] name = "block" @@ -491,16 +423,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "built" version = "0.7.4" @@ -521,22 +443,22 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -553,9 +475,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "calloop" @@ -591,12 +513,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.7" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -735,10 +658,10 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -855,6 +778,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cookie" version = "0.18.1" @@ -884,9 +813,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" @@ -935,9 +864,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -1076,6 +1005,19 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.79", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -1093,7 +1035,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "unicode-xid", ] @@ -1122,7 +1064,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1163,9 +1105,9 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "dwrote" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" dependencies = [ "lazy_static", "libc", @@ -1192,14 +1134,14 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1220,9 +1162,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.10" +version = "0.22.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f0eb73b934648cd7a4a61f1b15391cd95dab0b4da6e2e66c2a072c144b4a20" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" dependencies = [ "num-traits", ] @@ -1245,21 +1187,21 @@ dependencies = [ [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" dependencies = [ "simd-adler32", ] [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide 0.7.4", + "miniz_oxide 0.8.0", ] [[package]] @@ -1358,7 +1300,7 @@ dependencies = [ "core-text", "dwrote", "fontconfig-cache-parser", - "hashbrown 0.14.5", + "hashbrown", "icu_locid", "icu_properties", "memmap2", @@ -1379,7 +1321,7 @@ dependencies = [ "core-text", "dwrote", "fontconfig-cache-parser", - "hashbrown 0.14.5", + "hashbrown", "icu_locid", "memmap2", "objc2", @@ -1410,7 +1352,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1505,7 +1447,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1635,21 +1577,6 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "glyph-names" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3531d702d6c1a3ba92a5fb55a404c7b8c476c8e7ca249951077afcbe4bc807f" - -[[package]] -name = "gosub_bindings" -version = "0.1.0" -dependencies = [ - "gosub_html5", - "gosub_rendering", - "gosub_shared", -] - [[package]] name = "gosub_config" version = "0.1.0" @@ -1676,9 +1603,15 @@ dependencies = [ "anyhow", "colors-transform", "gosub_shared", + "itertools 0.13.0", "lazy_static", "log", + "nom 7.1.3", + "rand 0.8.5", + "serde", + "serde_json", "simple_logger", + "thiserror", ] [[package]] @@ -1691,7 +1624,7 @@ dependencies = [ "console_log", "cookie", "criterion", - "derive_more", + "derive_more 1.0.0", "futures", "getrandom", "gosub_config", @@ -1703,7 +1636,6 @@ dependencies = [ "gosub_renderer", "gosub_rendering", "gosub_shared", - "gosub_styling", "gosub_taffy", "gosub_testing", "gosub_useragent", @@ -1732,7 +1664,7 @@ name = "gosub_html5" version = "0.1.0" dependencies = [ "criterion", - "derive_more", + "derive_more 1.0.0", "gosub_css3", "gosub_shared", "gosub_testing", @@ -1760,7 +1692,7 @@ version = "0.1.0" dependencies = [ "anyhow", "cookie", - "derive_more", + "derive_more 1.0.0", "domain-lookup-tree", "gosub_config", "gosub_shared", @@ -1777,10 +1709,13 @@ dependencies = [ name = "gosub_render_backend" version = "0.1.0" dependencies = [ + "anyhow", + "gosub_css3", "gosub_html5", "gosub_shared", "gosub_typeface", "image", + "log", "raw-window-handle", "smallvec", ] @@ -1796,7 +1731,6 @@ dependencies = [ "gosub_render_backend", "gosub_rendering", "gosub_shared", - "gosub_styling", "image", "log", "url", @@ -1812,7 +1746,8 @@ dependencies = [ "gosub_css3", "gosub_html5", "gosub_render_backend", - "gosub_styling", + "gosub_shared", + "log", "regex", "rstar", ] @@ -1824,6 +1759,7 @@ dependencies = [ "anyhow", "chardet", "chardetng", + "derive_more 0.99.18", "encoding_rs", "getrandom", "js-sys", @@ -1836,31 +1772,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gosub_styling" -version = "0.1.0" -dependencies = [ - "anyhow", - "backtrace", - "colors-transform", - "gosub_css3", - "gosub_html5", - "gosub_render_backend", - "gosub_shared", - "gosub_typeface", - "itertools 0.13.0", - "lazy_static", - "log", - "memoize", - "nom 7.1.3", - "rand 0.9.0-alpha.2", - "regex", - "rust-fontconfig", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "gosub_svg" version = "0.1.0" @@ -1880,7 +1791,6 @@ dependencies = [ "anyhow", "gosub_render_backend", "gosub_shared", - "gosub_styling", "gosub_typeface", "log", "parley", @@ -1963,7 +1873,7 @@ version = "0.1.0" dependencies = [ "anyhow", "colored", - "derive_more", + "derive_more 1.0.0", "gosub_shared", "gosub_v8", "gosub_webinterop", @@ -1981,7 +1891,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2024,7 +1934,7 @@ checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -2080,22 +1990,13 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash", "allocator-api2", ] @@ -2124,12 +2025,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -2339,7 +2234,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2415,12 +2310,12 @@ checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -2440,7 +2335,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -2457,17 +2352,17 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -2576,11 +2471,11 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kurbo" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "smallvec", ] @@ -2617,9 +2512,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.158" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libfuzzer-sys" @@ -2708,15 +2603,6 @@ dependencies = [ "imgref", ] -[[package]] -name = "lru" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" -dependencies = [ - "hashbrown 0.12.3", -] - [[package]] name = "lru-cache" version = "0.1.2" @@ -2758,36 +2644,13 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] -[[package]] -name = "memoize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df4051db13d0816cf23196d3baa216385ae099339f5d0645a8d9ff2305e82b8" -dependencies = [ - "lazy_static", - "lru", - "memoize-inner", -] - -[[package]] -name = "memoize-inner" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bdece7e91f0d1e33df7b46ec187a93ea0d4e642113a1039ac8bfdd4a3273ac" -dependencies = [ - "lazy_static", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "metal" version = "0.29.0" @@ -2826,7 +2689,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", - "simd-adler32", ] [[package]] @@ -2836,13 +2698,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", @@ -2850,23 +2713,13 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "mmapio" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0204e2cac68f5b2e35b7ec8cb5d906f6e58e78dad8066a30b6ee54da99bb03dd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "naga" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bit-set 0.5.3", "bitflags 2.6.0", "codespan-reporting", @@ -2886,7 +2739,7 @@ version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bit-set 0.6.0", "bitflags 2.6.0", "cfg_aliases 0.1.1", @@ -3017,7 +2870,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3068,7 +2921,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3294,18 +3147,21 @@ dependencies = [ [[package]] name = "object" -version = "0.36.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "oorandom" @@ -3328,30 +3184,6 @@ dependencies = [ "libredox", ] -[[package]] -name = "ouroboros" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" -dependencies = [ - "aliasable", - "ouroboros_macro", - "static_assertions", -] - -[[package]] -name = "ouroboros_macro" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "owned_ttf_parser" version = "0.24.0" @@ -3379,7 +3211,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.6", "smallvec", "windows-targets 0.52.6", ] @@ -3447,7 +3279,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3482,7 +3314,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3499,15 +3331,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -3518,37 +3350,37 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide 0.8.0", ] [[package]] name = "polling" -version = "3.7.2" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", @@ -3556,7 +3388,7 @@ dependencies = [ "pin-project-lite", "rustix", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3571,6 +3403,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3579,11 +3417,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.6.6", + "zerocopy", ] [[package]] @@ -3594,45 +3432,21 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] @@ -3660,7 +3474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3686,9 +3500,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.34.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] @@ -3721,7 +3535,7 @@ checksum = "c3e256ff62cee3e03def855c4d4260106d2bb1696fdc01af03e9935b993720a5" dependencies = [ "rand_chacha 0.9.0-alpha.2", "rand_core 0.9.0-alpha.2", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -3760,7 +3574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4e93f5a5e3c528cda9acb0928c31b2ba868c551cc46e67b778075e34aab9906" dependencies = [ "getrandom", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -3777,7 +3591,7 @@ checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "av1-grain", "bitstream-io", "built", @@ -3806,9 +3620,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5797d09f9bd33604689e87e8380df4951d4912f01b63f71205e2abd4ae25e6b6" +checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" dependencies = [ "avif-serialize", "imgref", @@ -3875,18 +3689,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -3896,9 +3710,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -3907,9 +3721,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "renderdoc-sys" @@ -3946,9 +3760,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.45" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] @@ -3991,18 +3805,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "rust-fontconfig" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6002602a06c22f5f4e5ea2ed68854bfce071b48e1092c03874e34d0b59eb4b" -dependencies = [ - "allsorts", - "mmapio", - "rayon", - "xmlparser", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4015,11 +3817,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -4030,9 +3841,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "log", "once_cell", @@ -4045,15 +3856,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -4134,6 +3945,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.210" @@ -4151,7 +3968,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4169,9 +3986,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -4416,9 +4233,9 @@ checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" [[package]] name = "svgtypes" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" dependencies = [ "kurbo", "siphasher 1.0.1", @@ -4448,9 +4265,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -4465,7 +4282,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4475,7 +4292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck 0.5.0", + "heck", "pkg-config", "toml", "version-compare", @@ -4487,7 +4304,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cb893bff0f80ae17d3a57e030622a967b8dbc90e38284d9b4b1442e23873c94" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "grid", "num-traits", "serde", @@ -4527,7 +4344,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4538,7 +4355,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "test-case-core", ] @@ -4568,7 +4385,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4622,7 +4439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bytemuck", "cfg-if", "log", @@ -4678,9 +4495,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -4693,14 +4510,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.17" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a44eede9b727419af8095cb2d72fab15487a541f54647ad4414b34096ee4631" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.18", + "toml_edit", ] [[package]] @@ -4714,26 +4531,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1490595c74d930da779e944f5ba2ecdf538af67df1a9848cbd156af43c1b7cf0" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.16", + "winnow", ] [[package]] @@ -4755,7 +4561,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4788,12 +4594,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -4812,12 +4612,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" -[[package]] -name = "unicode-canonical-combining-class" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6925586af9268182c711e47c0853ed84131049efaca41776d0ca97f983865c32" - [[package]] name = "unicode-ccc" version = "0.2.0" @@ -4830,50 +4624,38 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" -[[package]] -name = "unicode-general-category" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" - [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-joining-type" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f8cb47ccb8bc750808755af3071da4a10dcd147b68fc874b7ae4b12543f6f5" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-script" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-vo" @@ -4883,15 +4665,15 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" @@ -5019,7 +4801,7 @@ dependencies = [ "miniz_oxide 0.7.4", "once_cell", "paste", - "which 6.0.2", + "which 6.0.3", ] [[package]] @@ -5180,7 +4962,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -5214,7 +4996,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5248,14 +5030,14 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] name = "wayland-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", @@ -5267,9 +5049,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" dependencies = [ "bitflags 2.6.0", "rustix", @@ -5290,9 +5072,9 @@ dependencies = [ [[package]] name = "wayland-cursor" -version = "0.31.5" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" +checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" dependencies = [ "rustix", "wayland-client", @@ -5301,9 +5083,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.3" +version = "0.32.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" +checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -5313,9 +5095,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79f2d57c7fcc6ab4d602adba364bf59a5c24de57bd194486bf9b8360e06bfc4" +checksum = "8a0a41a6875e585172495f7a96dfa42ca7e0213868f4f15c313f7c33221a7eff" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -5326,9 +5108,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -5339,9 +5121,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", "quick-xml", @@ -5350,9 +5132,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", @@ -5382,9 +5164,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -5401,7 +5183,7 @@ version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "cfg_aliases 0.1.1", "document-features", "js-sys", @@ -5426,7 +5208,7 @@ version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bit-vec 0.7.0", "bitflags 2.6.0", "cfg_aliases 0.1.1", @@ -5452,7 +5234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "ash", "bit-set 0.6.0", "bitflags 2.6.0", @@ -5515,9 +5297,9 @@ dependencies = [ [[package]] name = "which" -version = "6.0.2" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", @@ -5555,11 +5337,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5614,6 +5396,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -5798,7 +5589,7 @@ version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" dependencies = [ - "ahash 0.8.11", + "ahash", "android-activity", "atomic-waker", "bitflags 2.6.0", @@ -5846,18 +5637,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.16" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -5927,9 +5709,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" [[package]] name = "xkbcommon-dl" @@ -5952,15 +5734,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" - -[[package]] -name = "xmlparser" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" [[package]] name = "xmlwriter" @@ -5994,7 +5770,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "synstructure", ] @@ -6004,34 +5780,14 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" -[[package]] -name = "zerocopy" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" -dependencies = [ - "byteorder", - "zerocopy-derive 0.6.6", -] - [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy-derive" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", + "byteorder", + "zerocopy-derive", ] [[package]] @@ -6042,7 +5798,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6062,7 +5818,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", "synstructure", ] @@ -6091,7 +5847,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 17fe717c1..fa96aad2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ gosub_shared = { path = "./crates/gosub_shared", features = [] } gosub_config = { path = "./crates/gosub_config", features = [] } gosub_html5 = { path = "./crates/gosub_html5", features = [] } gosub_css3 = { path = "./crates/gosub_css3", features = [] } -gosub_styling = { path = "./crates/gosub_styling", features = [] } gosub_jsapi = { path = "./crates/gosub_jsapi", features = [] } gosub_testing = { path = "./crates/gosub_testing", features = [] } gosub_rendering = { path = "crates/gosub_render_utils", features = [] } diff --git a/benches/tree_iterator.rs b/benches/tree_iterator.rs index 0532ab999..639246007 100644 --- a/benches/tree_iterator.rs +++ b/benches/tree_iterator.rs @@ -1,10 +1,13 @@ use std::fs::File; use criterion::{criterion_group, criterion_main, Criterion}; -use gosub_html5::node::NodeId; -use gosub_html5::parser::document::{Document, DocumentBuilder, TreeIterator}; +use gosub_css3::system::Css3System; +use gosub_html5::document::builder::DocumentBuilderImpl; +use gosub_html5::document::document_impl::TreeIterator; use gosub_html5::parser::Html5Parser; use gosub_shared::byte_stream::{ByteStream, Encoding}; +use gosub_shared::node::NodeId; +use gosub_shared::traits::document::DocumentBuilder; fn wikipedia_main_page(c: &mut Criterion) { // Criterion can report inconsistent results from run to run in some cases. We attempt to @@ -17,13 +20,12 @@ fn wikipedia_main_page(c: &mut Criterion) { let mut stream = ByteStream::new(Encoding::UTF8, None); let _ = stream.read_from_file(html_file); - let main_document = DocumentBuilder::new_document(None); - let document = Document::clone(&main_document); - let _ = Html5Parser::parse_document(&mut stream, document, None); + let doc_handle = >::new_document(None); + let _ = Html5Parser::parse_document(&mut stream, doc_handle.clone(), None); group.bench_function("wikipedia main page", |b| { b.iter(|| { - let tree_iterator = TreeIterator::new(&main_document); + let tree_iterator = TreeIterator::new(doc_handle.clone()); let _ = tree_iterator.collect::>(); }) }); @@ -43,13 +45,12 @@ fn stackoverflow_home(c: &mut Criterion) { let mut bytestream = ByteStream::new(Encoding::UTF8, None); let _ = bytestream.read_from_file(html_file); - let main_document = DocumentBuilder::new_document(None); - let document = Document::clone(&main_document); - let _ = Html5Parser::parse_document(&mut bytestream, document, None); + let doc_handle = >::new_document(None); + let _ = Html5Parser::parse_document(&mut bytestream, doc_handle.clone(), None); group.bench_function("stackoverflow home", |b| { b.iter(|| { - let tree_iterator = TreeIterator::new(&main_document); + let tree_iterator = TreeIterator::new(doc_handle.clone()); let _ = tree_iterator.collect::>(); }) }); diff --git a/crates/gosub_bindings/.gitignore b/crates/gosub_bindings/.gitignore deleted file mode 100644 index e9b24178c..000000000 --- a/crates/gosub_bindings/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -libgosub.a -lib/ -*.dSYM diff --git a/crates/gosub_bindings/Cargo.toml b/crates/gosub_bindings/Cargo.toml deleted file mode 100644 index e6d390d72..000000000 --- a/crates/gosub_bindings/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "gosub_bindings" -version = "0.1.0" -edition = "2021" -authors = ["Gosub Community "] -license = "MIT" - -[dependencies] -gosub_shared = { path = "../gosub_shared" } -gosub_rendering = { path = "../gosub_render_utils" } -gosub_html5 = { path = "../gosub_html5" } - -[lib] -crate-type = ["staticlib"] diff --git a/crates/gosub_bindings/Makefile b/crates/gosub_bindings/Makefile deleted file mode 100644 index 796c1fe1b..000000000 --- a/crates/gosub_bindings/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -# "global" compile settings - -# compilation mode Release or Debug -MODE?=Release - -CFLAGS_DEBUG := -std=c99 -g -Wall -Wextra -O0 -CFLAGS_RELEASE := -std=c99 -Wall -Wextra -O2 - -TARGET_DIR_DEBUG := ../../target/debug -TARGET_DIR_RELEASE := ../../target/release - -ifeq ($(MODE), Release) -$(info ***** COMPILING IN RELEASE MODE *****) -CFLAGS = $(CFLAGS_RELEASE) -TARGET_DIR = $(TARGET_DIR_RELEASE) -else ifeq ($(MODE), Debug) -$(info ***** COMPILING IN DEBUG MODE *****) -CFLAGS = $(CFLAGS_DEBUG) -TARGET_DIR = $(TARGET_DIR_DEBUG) -else -$(warning ***** UNKNOWN MODE. DEFAULTING TO RELEASE MODE *****) -CFLAGS = $(CFLAGS_RELEASE) -TARGET_DIR = $(TARGET_DIR_RELEASE) -endif - -CC := gcc - -INCLUDE_DIR := include -SRC_DIR := src -NODE_SRC_DIR := $(SRC_DIR)/nodes - -CPPFLAGS := -I$(INCLUDE_DIR) -I$(INCLUDE_DIR)/nodes -LDFLAGS := -L$(TARGET_DIR) - -bindings: # build gosub static library to be used externally -ifeq ($(MODE), Debug) - cargo build -else - cargo build --release -endif - $(CC) $(CFLAGS) $(CPPFLAGS) -c $(SRC_DIR)/gosub_api.c $(SRC_DIR)/nodes.c $(NODE_SRC_DIR)/text.c - ar rcs libgosub.a gosub_api.o nodes.o text.o - rm *.o - test -d lib || mkdir lib - cp $(TARGET_DIR)/libgosub_bindings.a lib/ - cp ./libgosub.a lib/ - -TEST_SRC_DIR := tests -test: # build and run tests for bindings - $(CC) $(TEST_SRC_DIR)/rendertree_test.c -L./ -lgosub $(LDFLAGS) -lgosub_bindings -lsqlite3 -lm -o $(TEST_SRC_DIR)/rendertree_test $(CPPFLAGS) $(CFLAGS) - ./$(TEST_SRC_DIR)/rendertree_test - -format: - cargo clippy --fix --allow-dirty --allow-staged diff --git a/crates/gosub_bindings/README.md b/crates/gosub_bindings/README.md deleted file mode 100644 index 2323dbf43..000000000 --- a/crates/gosub_bindings/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Gosub Bindings -These are bindings that expose some of Gosub's engine to the world via a C API. Typically these bindings will be used by user agents. - -## Building -By default, the bindings will be built in release mode. You can modify this by specifying a `MODE` variable: -```text -export MODE=Debug # or MODE=Release (default) -make bindings -make test -``` - -or alternatively specify it manually (not recommended) -```text -make bindings MODE=Debug -make test MODE=Debug -``` - -This approach is not recommended because if you forget to specify it, it will default to release mode and you may be using the wrong version. diff --git a/crates/gosub_bindings/include/gosub_api.h b/crates/gosub_bindings/include/gosub_api.h deleted file mode 100644 index c0a7389ab..000000000 --- a/crates/gosub_bindings/include/gosub_api.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef GOSUB_API_H -#define GOSUB_API_H - -#include "nodes.h" - -extern void *gosub_rendertree_init(const char *html); -extern void *gosub_rendertree_iterator_init(void *rendertree); -extern const void *gosub_rendertree_next_node(void *tree_iterator); -extern void gosub_rendertree_get_node_data(const void *current_node, - struct node_t *node); -extern void gosub_rendertree_iterator_free(void *tree_iterator); -extern void gosub_rendertree_free(void *render_free); - -struct rendertree_t { - void *tree; - void *iterator; - const void *current_node; - struct node_t *data; -}; - -/// Initialize a render tree by passing a stack-allocated -/// struct by address. -/// Returns 0 on success or -1 if a failure occurred. -int8_t rendertree_init(struct rendertree_t *rendertree, const char *html); - -/// Get the next node in the render tree as a read-only pointer. -/// Returns NULL when reaching end of tree. -const struct node_t *rendertree_next(struct rendertree_t *rendertree); - -/// Get the type of the current node the render tree is pointing to. -enum node_type_e -rendertree_get_current_node_type(const struct rendertree_t *rendertree); - -/// Free all memory tied to the render tree -void rendertree_free(struct rendertree_t *rendertree); - -#endif diff --git a/crates/gosub_bindings/include/nodes.h b/crates/gosub_bindings/include/nodes.h deleted file mode 100644 index d4c55c369..000000000 --- a/crates/gosub_bindings/include/nodes.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef GOSUB_API_NODES_H -#define GOSUB_API_NODES_H - -#include -#include // for NULL (which is basically just 0... but more clear) -#include -#include - -#include "nodes/text.h" -#include "properties.h" - -struct node_t *rendertree_node_init(); -void rendertree_node_free(struct node_t **node); - -enum node_type_e { NODE_TYPE_ROOT = 0u, NODE_TYPE_TEXT }; - -struct node_t { - enum node_type_e type; - struct position_t position; - struct rectangle_t margin; - struct rectangle_t padding; - union data { - bool root; // NODE_TYPE_ROOT - struct node_text_t text; // NODE_TYPE_TEXT - } data; -}; - -struct node_t *rendertree_node_init(); -double rendertree_node_get_x(const struct node_t *node); -double rendertree_node_get_y(const struct node_t *node); -double rendertree_node_get_margin_top(const struct node_t *node); -double rendertree_node_get_margin_left(const struct node_t *node); -double rendertree_node_get_margin_right(const struct node_t *node); -double rendertree_node_get_margin_bottom(const struct node_t *node); -double rendertree_node_get_padding_top(const struct node_t *node); -double rendertree_node_get_padding_left(const struct node_t *node); -double rendertree_node_get_padding_right(const struct node_t *node); -double rendertree_node_get_padding_bottom(const struct node_t *node); -void rendertree_node_free_data(struct node_t *node); -void rendertree_node_free(struct node_t **node); - -#endif diff --git a/crates/gosub_bindings/include/nodes/text.h b/crates/gosub_bindings/include/nodes/text.h deleted file mode 100644 index 9873a0193..000000000 --- a/crates/gosub_bindings/include/nodes/text.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef GOSUB_API_NODES_TEXT_H -#define GOSUB_API_NODES_TEXT_H - -#include -#include - -struct node_t; - -struct node_text_t { - // this tag is not used but is required to map properly from Rust - uint32_t tag; - char *value; - char *font; - double font_size; - bool is_bold; -}; - -void rendertree_node_text_free_data(struct node_text_t *text); - -const char *rendertree_node_text_get_value(const struct node_t *node); -const char *rendertree_node_text_get_font(const struct node_t *node); -double rendertree_node_text_get_font_size(const struct node_t *node); -bool rendertree_node_text_get_bold(const struct node_t *node); - -#endif diff --git a/crates/gosub_bindings/include/properties.h b/crates/gosub_bindings/include/properties.h deleted file mode 100644 index d9502bf8d..000000000 --- a/crates/gosub_bindings/include/properties.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef GOSUB_API_PROPERTIES_H -#define GOSUB_API_PROPERTIES_H - -struct position_t { - double x; - double y; -}; - -struct rectangle_t { - double top; - double left; - double right; - double bottom; -}; - -#endif diff --git a/crates/gosub_bindings/src/gosub_api.c b/crates/gosub_bindings/src/gosub_api.c deleted file mode 100644 index 005a9c1e5..000000000 --- a/crates/gosub_bindings/src/gosub_api.c +++ /dev/null @@ -1,50 +0,0 @@ -#include "gosub_api.h" - -int8_t rendertree_init(struct rendertree_t *rendertree, const char *html) { - - rendertree->tree = gosub_rendertree_init(html); - if (!rendertree->tree) { - return -1; - } - - rendertree->iterator = gosub_rendertree_iterator_init(rendertree->tree); - if (!rendertree->iterator) { - gosub_rendertree_free(rendertree->tree); - return -1; - } - - rendertree->current_node = NULL; - - rendertree->data = rendertree_node_init(); - if (!rendertree->data) { - gosub_rendertree_iterator_free(rendertree->iterator); - gosub_rendertree_free(rendertree->tree); - return -1; - } - - return 0; -} - -const struct node_t *rendertree_next(struct rendertree_t *rendertree) { - rendertree_node_free_data(rendertree->data); - rendertree->current_node = - gosub_rendertree_next_node(rendertree->iterator); - if (!rendertree->current_node) - return NULL; - gosub_rendertree_get_node_data(rendertree->current_node, rendertree->data); - return (const struct node_t *)rendertree->data; -} - -enum node_type_e -rendertree_get_current_node_type(const struct rendertree_t *rendertree) { - return rendertree->data->type; -} - -void rendertree_free(struct rendertree_t *rendertree) { - gosub_rendertree_iterator_free(rendertree->iterator); - rendertree->iterator = NULL; - gosub_rendertree_free(rendertree->tree); - rendertree->tree = NULL; - rendertree_node_free(&rendertree->data); - rendertree->data = NULL; -} diff --git a/crates/gosub_bindings/src/lib.rs b/crates/gosub_bindings/src/lib.rs deleted file mode 100644 index 0a61eb7c3..000000000 --- a/crates/gosub_bindings/src/lib.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::ffi::c_char; -use std::ffi::CStr; -use std::ptr; - -pub mod wrapper; - -use gosub_html5::parser::document::{Document, DocumentBuilder}; -use gosub_html5::parser::Html5Parser; -use gosub_rendering::render_tree::{Node, NodeType, RenderTree, TreeIterator}; -use gosub_shared::byte_stream::{ByteStream, Encoding}; -use wrapper::node::CNode; - -/// Initialize a render tree and return an owning pointer to it. -/// If the HTML fails to parse or the html string fails to be converted to Rust, -/// returns a NULL pointer. -/// -/// # Safety -/// Takes a read-only pointer owned from the C API representing the HTML source -/// to build a render tree. DO NOT take ownership of this pointer in Rust or the -/// universe might collapse. -/// -/// Moves an owning pointer to the rendertree using Box::into_raw() to the C API. -/// This pointer MUST be passed to gosub_rendertree_free() after usage for proper cleanup. -#[no_mangle] -pub unsafe extern "C" fn gosub_rendertree_init(html: *const c_char) -> *mut RenderTree { - let html_str = unsafe { - if let Ok(html_str) = CStr::from_ptr(html).to_str() { - html_str - } else { - return ptr::null_mut(); - } - }; - let mut stream = ByteStream::new(Encoding::UTF8, None); - stream.read_from_str(html_str, Some(Encoding::UTF8)); - stream.close(); - - let doc = DocumentBuilder::new_document(None); - let parse_result = Html5Parser::parse_document(&mut stream, Document::clone(&doc), None); - - if parse_result.is_ok() { - let mut rendertree = Box::new(RenderTree::new(&doc)); - rendertree.build(); - - Box::into_raw(rendertree) - } else { - ptr::null_mut() - } -} - -/// Construct a tree iterator for a render tree and return an owning pointer to it. -/// -/// # Safety -/// Moves an owning pointer to the tree iterator using Box::into_raw() to the C API. -/// This pointer MUST be passed to gosub_rendertree_iterator_free() after usage for proper cleanup. -#[no_mangle] -pub unsafe extern "C" fn gosub_rendertree_iterator_init( - rendertree: *const RenderTree, -) -> *mut TreeIterator { - let tree_iterator = Box::new(TreeIterator::new(&(*rendertree))); - Box::into_raw(tree_iterator) -} - -/// Takes a tree_iterator and returns a non-owning pointer to the next node -/// -/// # Safety -/// Takes a tree_iterator pointer (owned by the C API generated by gosub_rendertree_iterator_init()) -/// and modifies it to point to the next tree-order node in the tree. Any heap-allocated data -/// on the current node is free'd before pointing to the next node. Returns a ready-only pointer -/// to the next node. -#[no_mangle] -pub unsafe extern "C" fn gosub_rendertree_next_node( - tree_iterator: *mut TreeIterator, -) -> *const Node { - let next = (*tree_iterator).next(); - if let Some(next) = next { - next.as_ptr() as *const Node - } else { - ptr::null() - } -} - -/// Fetch the node data according to the NodeType of the current node. -/// -/// # Safety -/// Uses a read-only pointer obtained from gosub_rendertree_next_node() -/// and a mutable pointer owned by the C API to write (copy) the contents -/// of the read-only pointer into the mutable pointer. -#[no_mangle] -pub unsafe extern "C" fn gosub_rendertree_get_node_data(node: *const Node, c_node: *mut CNode) { - // Change this to a match when we have more types - if let NodeType::Text(text_node) = &(*node).node_type { - *c_node = CNode::new_text(&*node, text_node); - } -} - -/// Free the iterator pointer obtained from gosub_rendertree_iterator_init() -/// -/// # Safety -/// This takes ownership of the pointer from the C API and transfers it to Rust so it can -/// be deallocated. -#[no_mangle] -pub unsafe extern "C" fn gosub_rendertree_iterator_free(tree_iterator: *mut TreeIterator) { - let _ = Box::from_raw(tree_iterator); -} - -/// Free the rendertree pointer obtained from gosub_rendertree_init() -/// -/// # Safety -/// This takes ownership of the pointer from the C API and transfers it to Rust so it can -/// be deallocated. -/// -// INTERNAL NOTE: It seems there's a leak happening with the document handle (if you -// check with valgrind) although I cannot figure out how to resolve this memory leak... -// needs more investigation; I've tried various methods. -#[no_mangle] -pub unsafe extern "C" fn gosub_rendertree_free(rendertree: *mut RenderTree) { - let _ = Box::from_raw(rendertree); -} diff --git a/crates/gosub_bindings/src/nodes.c b/crates/gosub_bindings/src/nodes.c deleted file mode 100644 index 4e88c52dd..000000000 --- a/crates/gosub_bindings/src/nodes.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "nodes.h" - -struct node_t *rendertree_node_init() { - struct node_t *node = malloc(sizeof(*node)); - if (!node) - return NULL; - - node->type = NODE_TYPE_ROOT; - node->data.root = true; // dummy value - - return node; -} - -void rendertree_node_free_data(struct node_t *node) { - switch (node->type) { - case NODE_TYPE_ROOT: - break; - case NODE_TYPE_TEXT: - rendertree_node_text_free_data(&node->data.text); - break; - } -} - -double rendertree_node_get_x(const struct node_t *node) { - return node->position.x; -} - -double rendertree_node_get_y(const struct node_t *node) { - return node->position.y; -} - -double rendertree_node_get_margin_top(const struct node_t *node) { - return node->margin.top; -} - -double rendertree_node_get_margin_left(const struct node_t *node) { - return node->margin.left; -} - -double rendertree_node_get_margin_right(const struct node_t *node) { - return node->margin.right; -} - -double rendertree_node_get_margin_bottom(const struct node_t *node) { - return node->margin.bottom; -} - -double rendertree_node_get_padding_top(const struct node_t *node) { - return node->padding.top; -} - -double rendertree_node_get_padding_left(const struct node_t *node) { - return node->padding.left; -} - -double rendertree_node_get_padding_right(const struct node_t *node) { - return node->padding.right; -} - -double rendertree_node_get_padding_bottom(const struct node_t *node) { - return node->padding.bottom; -} - -void rendertree_node_free(struct node_t **node) { - free(*node); - *node = NULL; -} diff --git a/crates/gosub_bindings/src/nodes/text.c b/crates/gosub_bindings/src/nodes/text.c deleted file mode 100644 index 76780ce25..000000000 --- a/crates/gosub_bindings/src/nodes/text.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "text.h" -#include "nodes.h" - -void rendertree_node_text_free_data(struct node_text_t *text) { - free(text->value); - text->value = NULL; - - free(text->font); - text->font = NULL; -} - -const char *rendertree_node_text_get_value(const struct node_t *node) { - if (!node) - return NULL; - - return (const char *)node->data.text.value; -} - -const char *rendertree_node_text_get_font(const struct node_t *node) { - if (!node) - return NULL; - - return (const char *)node->data.text.font; -} - -double rendertree_node_text_get_font_size(const struct node_t *node) { - if (!node) - return 0.0; - - return node->data.text.font_size; -} - -bool rendertree_node_text_get_bold(const struct node_t *node) { - if (!node) - return false; - - return node->data.text.is_bold; -} diff --git a/crates/gosub_bindings/src/wrapper.rs b/crates/gosub_bindings/src/wrapper.rs deleted file mode 100644 index 705f04f73..000000000 --- a/crates/gosub_bindings/src/wrapper.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod node; -pub mod text; - -/// Numerical values that map rendertree::NodeType to C -#[repr(C)] -pub enum CNodeType { - Root = 0, - Text = 1, -} diff --git a/crates/gosub_bindings/src/wrapper/node.rs b/crates/gosub_bindings/src/wrapper/node.rs deleted file mode 100644 index 10e35027f..000000000 --- a/crates/gosub_bindings/src/wrapper/node.rs +++ /dev/null @@ -1,47 +0,0 @@ -use gosub_rendering::render_tree::properties::{Position, Rectangle}; -use gosub_rendering::render_tree::{text::TextNode, Node}; - -use crate::wrapper::{text::CTextNode, CNodeType}; - -#[repr(C, u32)] -pub enum CNodeData { - Root(bool), - Text(CTextNode), -} - -#[repr(C)] -pub struct CNode { - pub tag: CNodeType, - pub position: Position, - pub margin: Rectangle, - pub padding: Rectangle, - pub data: CNodeData, -} - -impl Default for CNode { - fn default() -> Self { - Self { - tag: CNodeType::Root, - position: Position::new(), - margin: Rectangle::new(), - padding: Rectangle::new(), - data: CNodeData::Root(true), - } - } -} - -impl CNode { - pub fn new_root() -> Self { - Self::default() - } - - pub fn new_text(node: &Node, text_node: &TextNode) -> Self { - Self { - tag: CNodeType::Text, - position: node.position.clone(), - margin: node.margin.clone(), - padding: node.padding.clone(), - data: CNodeData::Text(CTextNode::from(text_node)), - } - } -} diff --git a/crates/gosub_bindings/src/wrapper/text.rs b/crates/gosub_bindings/src/wrapper/text.rs deleted file mode 100644 index 78c89312b..000000000 --- a/crates/gosub_bindings/src/wrapper/text.rs +++ /dev/null @@ -1,28 +0,0 @@ -use gosub_rendering::render_tree::text::TextNode; -use std::ffi::c_char; -use std::ffi::CString; - -/// This is a C-friendly wrapper around gosub_render_utils::rendertree::text::TextNode -/// that converts Rust Strings to owned pointers to pass to the C API. -#[repr(C)] -pub struct CTextNode { - pub value: *mut c_char, - pub font: *mut c_char, - pub font_size: f64, - pub is_bold: bool, -} - -impl From<&TextNode> for CTextNode { - fn from(text_node: &TextNode) -> Self { - Self { - value: CString::new(text_node.value.clone().into_bytes()) - .expect("Failed to allocate memory for text node value in CTextNode") - .into_raw(), - font: CString::new(text_node.font.clone().into_bytes()) - .expect("Failed to allocate memory for text node font in CTextNode") - .into_raw(), - font_size: text_node.font_size, - is_bold: text_node.is_bold, - } - } -} diff --git a/crates/gosub_bindings/tests/rendertree_test.c b/crates/gosub_bindings/tests/rendertree_test.c deleted file mode 100644 index dd8104214..000000000 --- a/crates/gosub_bindings/tests/rendertree_test.c +++ /dev/null @@ -1,121 +0,0 @@ -#include "gosub_api.h" -#include -#include -#include -#include - -int main() { - const char *html = "" - "

this is heading 1

" - "

this is heading 2

" - "

this is heading 3

" - "

this is heading 4

" - "
this is heading 5
" - "
this is heading 6
" - "

this is a paragraph

" - ""; - struct rendertree_t rendertree; - assert(rendertree_init(&rendertree, html) == 0); - - const struct node_t *node = NULL; - - // - node = rendertree_next(&rendertree); - assert(node->type == NODE_TYPE_ROOT); - - const double tol = 0.00001; - - double y = 0.00; - - //

- node = rendertree_next(&rendertree); - y += rendertree_node_get_margin_top(node); - assert(node->type == NODE_TYPE_TEXT); - assert(strcmp(rendertree_node_text_get_value(node), "this is heading 1") == 0); - assert(strcmp(rendertree_node_text_get_font(node), "Times New Roman") == 0); - assert(fabs(rendertree_node_text_get_font_size(node) - 37.0) < tol); - assert(rendertree_node_text_get_bold(node) == true); - assert(fabs(rendertree_node_get_x(node) - 0.00) < tol); - assert(fabs(rendertree_node_get_y(node) - y) < tol); - y += (rendertree_node_text_get_font_size(node) + rendertree_node_get_margin_bottom(node)); - - //

- node = rendertree_next(&rendertree); - y += rendertree_node_get_margin_top(node); - assert(node->type == NODE_TYPE_TEXT); - assert(strcmp(rendertree_node_text_get_value(node), "this is heading 2") == 0); - assert(strcmp(rendertree_node_text_get_font(node), "Times New Roman") == 0); - assert(fabs(rendertree_node_text_get_font_size(node) - 27.5) < tol); - assert(rendertree_node_text_get_bold(node) == true); - assert(fabs(rendertree_node_get_x(node) - 0.00) < tol); - assert(fabs(rendertree_node_get_y(node) - y) < tol); - y += (rendertree_node_text_get_font_size(node) + rendertree_node_get_margin_bottom(node)); - - //

- node = rendertree_next(&rendertree); - y += rendertree_node_get_margin_top(node); - assert(node->type == NODE_TYPE_TEXT); - assert(strcmp(rendertree_node_text_get_value(node), "this is heading 3") == 0); - assert(strcmp(rendertree_node_text_get_font(node), "Times New Roman") == 0); - assert(fabs(rendertree_node_text_get_font_size(node) - 21.5) < tol); - assert(rendertree_node_text_get_bold(node) == true); - assert(fabs(rendertree_node_get_x(node) - 0.00) < tol); - assert(fabs(rendertree_node_get_y(node) - y) < tol); - y += (rendertree_node_text_get_font_size(node) + rendertree_node_get_margin_bottom(node)); - - //

- node = rendertree_next(&rendertree); - y += rendertree_node_get_margin_top(node); - assert(node->type == NODE_TYPE_TEXT); - assert(strcmp(rendertree_node_text_get_value(node), "this is heading 4") == 0); - assert(strcmp(rendertree_node_text_get_font(node), "Times New Roman") == 0); - assert(fabs(rendertree_node_text_get_font_size(node) - 18.5) < tol); - assert(rendertree_node_text_get_bold(node) == true); - assert(fabs(rendertree_node_get_x(node) - 0.00) < tol); - assert(fabs(rendertree_node_get_y(node) - y) < tol); - y += (rendertree_node_text_get_font_size(node) + rendertree_node_get_margin_bottom(node)); - - //

- node = rendertree_next(&rendertree); - y += rendertree_node_get_margin_top(node); - assert(node->type == NODE_TYPE_TEXT); - assert(strcmp(rendertree_node_text_get_value(node), "this is heading 5") == 0); - assert(strcmp(rendertree_node_text_get_font(node), "Times New Roman") == 0); - assert(fabs(rendertree_node_text_get_font_size(node) - 15.5) < tol); - assert(rendertree_node_text_get_bold(node) == true); - assert(fabs(rendertree_node_get_x(node) - 0.00) < tol); - assert(fabs(rendertree_node_get_y(node) - y) < tol); - y += (rendertree_node_text_get_font_size(node) + rendertree_node_get_margin_bottom(node)); - - //
- node = rendertree_next(&rendertree); - y += rendertree_node_get_margin_top(node); - assert(node->type == NODE_TYPE_TEXT); - assert(strcmp(rendertree_node_text_get_value(node), "this is heading 6") == 0); - assert(strcmp(rendertree_node_text_get_font(node), "Times New Roman") == 0); - assert(fabs(rendertree_node_text_get_font_size(node) - 12.0) < tol); - assert(rendertree_node_text_get_bold(node) == true); - assert(fabs(rendertree_node_get_x(node) - 0.00) < tol); - assert(fabs(rendertree_node_get_y(node) - y) < tol); - y += (rendertree_node_text_get_font_size(node) + rendertree_node_get_margin_bottom(node)); - - //

- node = rendertree_next(&rendertree); - y += rendertree_node_get_margin_top(node); - assert(node->type == NODE_TYPE_TEXT); - assert(strcmp(rendertree_node_text_get_value(node), "this is a paragraph") == 0); - assert(strcmp(rendertree_node_text_get_font(node), "Times New Roman") == 0); - assert(fabs(rendertree_node_text_get_font_size(node) - 18.5) < tol); - assert(rendertree_node_text_get_bold(node) == false); - assert(fabs(rendertree_node_get_x(node) - 0.00) < tol); - assert(fabs(rendertree_node_get_y(node) - y) < tol); - - // end of iterator, last node is free'd - node = rendertree_next(&rendertree); - assert(node == NULL); - - rendertree_free(&rendertree); - - printf("\033[0;32mrendertree_test.c: All assertions passed\n\033[0;30m"); - return 0; -} diff --git a/crates/gosub_config/Cargo.toml b/crates/gosub_config/Cargo.toml index 4cb6a1cdb..3d8ba12dd 100644 --- a/crates/gosub_config/Cargo.toml +++ b/crates/gosub_config/Cargo.toml @@ -2,6 +2,9 @@ name = "gosub_config" version = "0.1.0" edition = "2021" +authors = ["Gosub Community "] +license = "MIT" + [dependencies] gosub_shared = { path = "../gosub_shared", features = [] } diff --git a/crates/gosub_config/src/lib.rs b/crates/gosub_config/src/lib.rs index 40e5f9472..4129e4918 100644 --- a/crates/gosub_config/src/lib.rs +++ b/crates/gosub_config/src/lib.rs @@ -166,11 +166,7 @@ impl ConfigStore { // Find all keys, and add them to the configuration store if let Ok(all_settings) = self.storage.all() { for (key, value) in all_settings { - self.settings - .lock() - .unwrap() - .borrow_mut() - .insert(key, value); + self.settings.lock().unwrap().borrow_mut().insert(key, value); } } } @@ -258,14 +254,12 @@ impl ConfigStore { /// Populates the settings in the storage from the settings.json file fn populate_default_settings(&mut self) -> Result<()> { - let json_data: Value = - serde_json::from_str(SETTINGS_JSON).expect("Failed to parse settings.json"); + let json_data: Value = serde_json::from_str(SETTINGS_JSON).expect("Failed to parse settings.json"); if let Value::Object(data) = json_data { for (section_prefix, section_entries) in &data { let section_entries: Vec = - serde_json::from_value(section_entries.clone()) - .expect("Failed to parse settings.json"); + serde_json::from_value(section_entries.clone()).expect("Failed to parse settings.json"); for entry in section_entries { let key = format!("{}.{}", section_prefix, entry.key); @@ -317,10 +311,7 @@ mod test { assert_eq!(captured_logs.len(), 0); }); - config_store_write().set( - "dns.local.enabled", - Setting::String("wont accept strings".into()), - ); + config_store_write().set("dns.local.enabled", Setting::String("wont accept strings".into())); testing_logger::validate(|captured_logs| { assert_eq!(captured_logs.len(), 1); diff --git a/crates/gosub_config/src/settings.rs b/crates/gosub_config/src/settings.rs index 56be2af6e..ec00529dd 100644 --- a/crates/gosub_config/src/settings.rs +++ b/crates/gosub_config/src/settings.rs @@ -118,8 +118,7 @@ impl<'de> Deserialize<'de> for Setting { D: Deserializer<'de>, { let value = String::deserialize(deserializer)?; - Setting::from_str(&value) - .map_err(|err| serde::de::Error::custom(format!("cannot deserialize: {err}"))) + Setting::from_str(&value).map_err(|err| serde::de::Error::custom(format!("cannot deserialize: {err}"))) } } @@ -241,10 +240,7 @@ mod test { assert_eq!(vec!("hello world"), s.to_map()); let s = Setting::from_str("m:foo,bar,baz").unwrap(); - assert_eq!( - s, - Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()]) - ); + assert_eq!(s, Setting::Map(vec!["foo".into(), "bar".into(), "baz".into()])); assert!(s.to_bool()); assert_eq!(3, s.to_sint()); assert_eq!(3, s.to_uint()); diff --git a/crates/gosub_config/src/storage/json.rs b/crates/gosub_config/src/storage/json.rs index be43b5e62..967e81f99 100644 --- a/crates/gosub_config/src/storage/json.rs +++ b/crates/gosub_config/src/storage/json.rs @@ -105,9 +105,7 @@ impl JsonStorageAdapter { let json = serde_json::to_string_pretty(&self.elements).expect("failed to serialize"); file.set_len(0).expect("failed to truncate file"); - file.seek(std::io::SeekFrom::Start(0)) - .expect("failed to seek"); - file.write_all(json.as_bytes()) - .expect("failed to write file"); + file.seek(std::io::SeekFrom::Start(0)).expect("failed to seek"); + file.write_all(json.as_bytes()).expect("failed to write file"); } } diff --git a/crates/gosub_config/src/storage/sqlite.rs b/crates/gosub_config/src/storage/sqlite.rs index 8790f6283..16b23fc3a 100644 --- a/crates/gosub_config/src/storage/sqlite.rs +++ b/crates/gosub_config/src/storage/sqlite.rs @@ -52,9 +52,7 @@ impl StorageAdapter for SqliteStorageAdapter { let query = "INSERT OR REPLACE INTO settings (key, value) VALUES (:key, :value)"; let mut statement = db_lock.prepare(query).unwrap(); statement.bind((":key", key)).unwrap(); - statement - .bind((":value", value.to_string().as_str())) - .unwrap(); + statement.bind((":value", value.to_string().as_str())).unwrap(); statement.next().unwrap(); } diff --git a/crates/gosub_css3/Cargo.toml b/crates/gosub_css3/Cargo.toml index 4245b8ab1..a674f7ed7 100644 --- a/crates/gosub_css3/Cargo.toml +++ b/crates/gosub_css3/Cargo.toml @@ -7,8 +7,16 @@ license = "MIT" [dependencies] gosub_shared = { path = "../gosub_shared", features = [] } -lazy_static = "1.5" +lazy_static = "1.5.0" log = "0.4.22" simple_logger = "5.0.0" anyhow = { version = "1.0.89", features = [] } -colors-transform = "0.2.11" \ No newline at end of file +colors-transform = "0.2.11" +rand = "0.8.5" +itertools = "0.13.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +serde = { version = "1.0.210", features = ["derive"] } +serde_json = "1.0.128" +thiserror = "1.0.63" +nom = "7.1.3" diff --git a/crates/gosub_styling/resources/definitions/definitions.json b/crates/gosub_css3/resources/definitions/definitions.json similarity index 100% rename from crates/gosub_styling/resources/definitions/definitions.json rename to crates/gosub_css3/resources/definitions/definitions.json diff --git a/crates/gosub_styling/resources/definitions/definitions_at-rules.json b/crates/gosub_css3/resources/definitions/definitions_at-rules.json similarity index 100% rename from crates/gosub_styling/resources/definitions/definitions_at-rules.json rename to crates/gosub_css3/resources/definitions/definitions_at-rules.json diff --git a/crates/gosub_styling/resources/definitions/definitions_properties.json b/crates/gosub_css3/resources/definitions/definitions_properties.json similarity index 100% rename from crates/gosub_styling/resources/definitions/definitions_properties.json rename to crates/gosub_css3/resources/definitions/definitions_properties.json diff --git a/crates/gosub_styling/resources/definitions/definitions_selectors.json b/crates/gosub_css3/resources/definitions/definitions_selectors.json similarity index 100% rename from crates/gosub_styling/resources/definitions/definitions_selectors.json rename to crates/gosub_css3/resources/definitions/definitions_selectors.json diff --git a/crates/gosub_styling/resources/definitions/definitions_values.json b/crates/gosub_css3/resources/definitions/definitions_values.json similarity index 100% rename from crates/gosub_styling/resources/definitions/definitions_values.json rename to crates/gosub_css3/resources/definitions/definitions_values.json diff --git a/crates/gosub_styling/resources/useragent.css b/crates/gosub_css3/resources/useragent.css similarity index 100% rename from crates/gosub_styling/resources/useragent.css rename to crates/gosub_css3/resources/useragent.css diff --git a/crates/gosub_css3/src/convert/ast_converter.rs b/crates/gosub_css3/src/ast.rs similarity index 79% rename from crates/gosub_css3/src/convert/ast_converter.rs rename to crates/gosub_css3/src/ast.rs index ba05aa2ca..c2fefe1f1 100644 --- a/crates/gosub_css3/src/convert/ast_converter.rs +++ b/crates/gosub_css3/src/ast.rs @@ -1,10 +1,10 @@ use crate::node::{Node as CssNode, NodeType}; use crate::stylesheet::{ - AttributeSelector, Combinator, CssDeclaration, CssOrigin, CssRule, CssSelector, - CssSelectorPart, CssStylesheet, CssValue, MatcherType, + AttributeSelector, Combinator, CssDeclaration, CssRule, CssSelector, CssSelectorPart, CssStylesheet, CssValue, + MatcherType, }; -use anyhow::anyhow; -use gosub_shared::types::Result; +use gosub_shared::errors::{CssError, CssResult}; +use gosub_shared::traits::css3::CssOrigin; use log::warn; /* @@ -60,19 +60,16 @@ vs */ /// Converts a CSS AST to a CSS stylesheet structure -pub fn convert_ast_to_stylesheet( - css_ast: &CssNode, - origin: CssOrigin, - location: &str, -) -> Result { +pub fn convert_ast_to_stylesheet(css_ast: &CssNode, origin: CssOrigin, url: &str) -> CssResult { if !css_ast.is_stylesheet() { - return Err(anyhow!("CSS AST must start with a stylesheet node")); + return Err(CssError::new("CSS AST must start with a stylesheet node")); } let mut sheet = CssStylesheet { rules: vec![], origin, - location: location.to_string(), + url: url.to_string(), + parse_log: vec![], }; for node in css_ast.as_stylesheet() { @@ -91,9 +88,7 @@ pub fn convert_ast_to_stylesheet( continue; } - let mut selector = CssSelector { - parts: vec![vec![]], - }; + let mut selector = CssSelector { parts: vec![vec![]] }; for node in node.as_selector_list().iter() { if !node.is_selector() { continue; @@ -111,24 +106,18 @@ pub fn convert_ast_to_stylesheet( " " => Combinator::Descendant, "||" => Combinator::Column, "|" => Combinator::Namespace, - _ => return Err(anyhow!("Unknown combinator: {}", value)), + _ => return Err(CssError::new(format!("Unknown combinator: {}", value).as_str())), }; CssSelectorPart::Combinator(combinator) } NodeType::IdSelector { value } => CssSelectorPart::Id(value.clone()), - NodeType::TypeSelector { value, .. } if value == "*" => { - CssSelectorPart::Universal - } - NodeType::PseudoClassSelector { value, .. } => { - CssSelectorPart::PseudoClass(value.to_string()) - } + NodeType::TypeSelector { value, .. } if value == "*" => CssSelectorPart::Universal, + NodeType::PseudoClassSelector { value, .. } => CssSelectorPart::PseudoClass(value.to_string()), NodeType::PseudoElementSelector { value, .. } => { CssSelectorPart::PseudoElement(value.to_string()) } - NodeType::TypeSelector { value, .. } => { - CssSelectorPart::Type(value.clone()) - } + NodeType::TypeSelector { value, .. } => CssSelectorPart::Type(value.clone()), NodeType::AttributeSelector { name, value, @@ -170,7 +159,9 @@ pub fn convert_ast_to_stylesheet( continue; } _ => { - return Err(anyhow!("Unsupported selector part: {:?}", node.node_type)) + return Err(CssError::new( + format!("Unsupported selector part: {:?}", node.node_type).as_str(), + )); } }; if let Some(x) = selector.parts.last_mut() { @@ -224,12 +215,12 @@ pub fn convert_ast_to_stylesheet( #[cfg(test)] mod tests { use super::*; - use crate::parser_config::ParserConfig; use crate::Css3; + use gosub_shared::traits::ParserConfig; #[test] fn convert_font_family() { - let ast = Css3::parse( + let stylesheet = Css3::parse_str( r#" body { border: 1px solid black; @@ -241,66 +232,42 @@ mod tests { } "#, ParserConfig::default(), + CssOrigin::User, + "test.css", ) .unwrap(); - let tree = convert_ast_to_stylesheet(&ast, CssOrigin::UserAgent, "test.css").unwrap(); - - dbg!(&tree); + dbg!(&stylesheet); } #[test] fn convert_test() { - let ast = Css3::parse( + let stylesheet = Css3::parse_str( r#" h1 { color: red; } h3, h4 { border: 1px solid black; } "#, ParserConfig::default(), + CssOrigin::User, + "test.css", ) .unwrap(); - let tree = convert_ast_to_stylesheet(&ast, CssOrigin::UserAgent, "test.css").unwrap(); - assert_eq!( - tree.rules - .first() - .unwrap() - .declarations - .first() - .unwrap() - .property, + stylesheet.rules.first().unwrap().declarations.first().unwrap().property, "color" ); assert_eq!( - tree.rules - .first() - .unwrap() - .declarations - .first() - .unwrap() - .value, + stylesheet.rules.first().unwrap().declarations.first().unwrap().value, vec![CssValue::String("red".into())] ); assert_eq!( - tree.rules - .get(1) - .unwrap() - .declarations - .first() - .unwrap() - .property, + stylesheet.rules.get(1).unwrap().declarations.first().unwrap().property, "border" ); assert_eq!( - tree.rules - .get(1) - .unwrap() - .declarations - .first() - .unwrap() - .value, + stylesheet.rules.get(1).unwrap().declarations.first().unwrap().value, vec![ CssValue::Unit(1.0, "px".into()), CssValue::String("solid".into()), diff --git a/crates/gosub_css3/src/colors.rs b/crates/gosub_css3/src/colors.rs index 7575cb3bc..75c576d75 100644 --- a/crates/gosub_css3/src/colors.rs +++ b/crates/gosub_css3/src/colors.rs @@ -76,12 +76,7 @@ impl From<&str> for RgbColor { return RgbColor::default(); } let rgb = rgb.unwrap(); - return RgbColor::new( - rgb.get_red(), - rgb.get_green(), - rgb.get_blue(), - rgb.get_alpha(), - ); + return RgbColor::new(rgb.get_red(), rgb.get_green(), rgb.get_blue(), rgb.get_alpha()); } if value.starts_with("hsl(") { let hsl = Hsl::from_str(value); @@ -99,12 +94,7 @@ impl From<&str> for RgbColor { return RgbColor::default(); } let rgb: Rgb = hsl.unwrap().to_rgb(); - return RgbColor::new( - rgb.get_red(), - rgb.get_green(), - rgb.get_blue(), - rgb.get_alpha(), - ); + return RgbColor::new(rgb.get_red(), rgb.get_green(), rgb.get_blue(), rgb.get_alpha()); } return get_hex_color_from_name(value).map_or(RgbColor::default(), parse_hex); @@ -146,12 +136,7 @@ fn parse_hex(value: &str) -> RgbColor { let r = i32::from_str_radix(&value[1..2], 16).unwrap(); let g = i32::from_str_radix(&value[2..3], 16).unwrap(); let b = i32::from_str_radix(&value[3..4], 16).unwrap(); - return RgbColor::new( - (r * 16 + r) as f32, - (g * 16 + g) as f32, - (b * 16 + b) as f32, - 255.0, - ); + return RgbColor::new((r * 16 + r) as f32, (g * 16 + g) as f32, (b * 16 + b) as f32, 255.0); } // 4 hex digits (RGBA) diff --git a/crates/gosub_css3/src/convert.rs b/crates/gosub_css3/src/convert.rs deleted file mode 100644 index 4637be32d..000000000 --- a/crates/gosub_css3/src/convert.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ast_converter; diff --git a/crates/gosub_css3/src/errors.rs b/crates/gosub_css3/src/errors.rs new file mode 100644 index 000000000..e76800e83 --- /dev/null +++ b/crates/gosub_css3/src/errors.rs @@ -0,0 +1,35 @@ +//! Error results that can be returned from the css3 parser +// use gosub_shared::byte_stream::Location; + +// +// /// Parser error that defines an error (message) on the given position +// #[derive(Clone, Debug, PartialEq)] +// pub struct CssError { +// /// Error message +// pub message: String, +// /// Location of the error, if available (during parsing mostly) +// pub location: Option, +// } +// +// impl CssError { +// pub fn new(message: &str, location: Option) -> Self { +// Self { +// message: message.to_string(), +// location, +// } +// } +// } + +// /// Serious errors and errors from third-party libraries +// #[derive(Debug, Error)] +// pub enum Error { +// #[error("parse error: {0} at {1}")] +// Parse(String, Location), +// +// #[allow(dead_code)] +// #[error("incorrect value: {0} at {1}")] +// IncorrectValue(String, Location), +// +// #[error("css failure: {0}")] +// CssFailure(String), +// } diff --git a/crates/gosub_css3/src/functions.rs b/crates/gosub_css3/src/functions.rs new file mode 100644 index 000000000..b8e327dd1 --- /dev/null +++ b/crates/gosub_css3/src/functions.rs @@ -0,0 +1,7 @@ +// pub use attr::*; +// pub use calc::*; +// pub use var::*; + +pub mod attr; +pub mod calc; +pub mod var; diff --git a/crates/gosub_css3/src/functions/attr.rs b/crates/gosub_css3/src/functions/attr.rs new file mode 100644 index 000000000..8de5951f8 --- /dev/null +++ b/crates/gosub_css3/src/functions/attr.rs @@ -0,0 +1,33 @@ +use crate::stylesheet::CssValue; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::node::{ElementDataType, Node}; + +// Probably this shouldn't quite be in gosub_css3 +#[allow(dead_code)] +pub fn resolve_attr, C: CssSystem>(values: &[CssValue], node: &N) -> Vec { + let Some(attr_name) = values.first().map(|v| v.to_string()) else { + return vec![]; + }; + + let ty = values.get(1).cloned(); + + let Some(data) = node.get_element_data() else { + return vec![]; + }; + + let Some(attr_value) = data.attribute(&attr_name) else { + let _default_value = values.get(2).cloned(); + + if let Some(ty) = ty { + return vec![ty]; + } + + return vec![]; + }; + + let Ok(value) = CssValue::parse_str(attr_value) else { + return vec![]; + }; + + vec![value] +} diff --git a/crates/gosub_styling/src/functions/calc.rs b/crates/gosub_css3/src/functions/calc.rs similarity index 58% rename from crates/gosub_styling/src/functions/calc.rs rename to crates/gosub_css3/src/functions/calc.rs index 5e24bd79f..c40bfcf56 100644 --- a/crates/gosub_styling/src/functions/calc.rs +++ b/crates/gosub_css3/src/functions/calc.rs @@ -1,5 +1,6 @@ -use gosub_css3::stylesheet::CssValue; +use crate::stylesheet::CssValue; +#[allow(dead_code)] pub fn resolve_calc(_values: &[CssValue]) -> Vec { todo!() } diff --git a/crates/gosub_styling/src/functions/var.rs b/crates/gosub_css3/src/functions/var.rs similarity index 74% rename from crates/gosub_styling/src/functions/var.rs rename to crates/gosub_css3/src/functions/var.rs index 1180456ca..1de85d88e 100644 --- a/crates/gosub_styling/src/functions/var.rs +++ b/crates/gosub_css3/src/functions/var.rs @@ -1,16 +1,18 @@ use std::collections::HashMap; -use gosub_css3::stylesheet::CssValue; -use gosub_html5::node::Node; -use gosub_html5::parser::document::Document; +use crate::stylesheet::CssValue; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::Document; +#[allow(dead_code)] #[derive(Clone, Debug, Default)] pub struct VariableEnvironment { pub values: HashMap, } +#[allow(dead_code)] impl VariableEnvironment { - pub fn get(&self, name: &str, _doc: &Document, _node: &Node) -> Option { + pub fn get, C: CssSystem>(&self, name: &str, _doc: &D, _node: &D::Node) -> Option { let mut current = Some(self); while let Some(env) = current { @@ -29,7 +31,8 @@ impl VariableEnvironment { } } -pub fn resolve_var(values: &[CssValue], doc: &Document, node: &Node) -> Vec { +#[allow(dead_code)] +pub fn resolve_var, C: CssSystem>(values: &[CssValue], doc: &D, node: &D::Node) -> Vec { let Some(name) = values.first().map(|v| { let mut str = v.to_string(); diff --git a/crates/gosub_css3/src/lib.rs b/crates/gosub_css3/src/lib.rs index 8c0a966f6..ab70d8cbf 100644 --- a/crates/gosub_css3/src/lib.rs +++ b/crates/gosub_css3/src/lib.rs @@ -1,98 +1,127 @@ -use crate::node::{Node, NodeType}; -use crate::parser_config::{Context, ParserConfig}; +use crate::ast::convert_ast_to_stylesheet; +use crate::stylesheet::CssStylesheet; use crate::tokenizer::Tokenizer; + use gosub_shared::byte_stream::{ByteStream, Encoding, Location}; +use gosub_shared::errors::{CssError, CssResult}; +use gosub_shared::traits::css3::CssOrigin; +use gosub_shared::traits::Context; +use gosub_shared::traits::ParserConfig; use gosub_shared::{timing_start, timing_stop}; +pub mod ast; +/// This CSS3 parser is heavily based on the MIT licensed CssTree parser written by +/// Roman Dvornov (https://github.com/lahmatiy). +/// The original version can be found at https://github.com/csstree/csstree pub mod colors; -pub mod convert; -mod node; +pub mod errors; +mod functions; +#[allow(dead_code)] +pub mod matcher; +pub mod node; pub mod parser; -pub mod parser_config; pub mod stylesheet; +pub mod system; pub mod tokenizer; mod unicode; pub mod walker; -/// This CSS3 parser is heavily based on the MIT licensed CssTree parser written by -/// Roman Dvornov (https://github.com/lahmatiy). -/// The original version can be found at https://github.com/csstree/csstree - pub struct Css3<'stream> { /// The tokenizer is responsible for reading the input stream and pub tokenizer: Tokenizer<'stream>, - /// When the last item is true, we allow values in argument lists. + /// When true, we allow values in argument lists. allow_values_in_argument_list: Vec, /// The parser configuration as given config: ParserConfig, -} - -#[derive(Debug)] -pub struct Error { - /// The error message - pub message: String, - /// The location of the error - pub location: Location, -} - -impl Error { - pub(crate) fn new(message: String, location: Location) -> Error { - Error { message, location } - } + /// Origin of the stream (useragent, inline etc.) + origin: CssOrigin, + /// Source of the stream (filename, url, etc.) + source: String, } impl<'stream> Css3<'stream> { - /// Parse a CSS string, which depends on the context. - pub fn parse(data: &str, config: ParserConfig) -> Result { - let t_id = timing_start!("css3.parse", config.source.as_deref().unwrap_or("")); + /// Creates a new parser with the given byte stream so only parse() needs to be called. + fn new(stream: &'stream mut ByteStream, config: ParserConfig, origin: CssOrigin, source: &str) -> Self { + Self { + tokenizer: Tokenizer::new(stream, Location::default()), + allow_values_in_argument_list: Vec::new(), + config, + origin, + source: source.to_string(), + } + } + /// Parses a direct string to a CssStyleSheet + pub fn parse_str( + data: &str, + config: ParserConfig, + origin: CssOrigin, + source_url: &str, + ) -> CssResult { let mut stream = ByteStream::new(Encoding::UTF8, None); stream.read_from_str(data, Some(Encoding::UTF8)); stream.close(); - let mut parser = Css3::new(&mut stream); - let result = parser.parse_internal(config); - - timing_stop!(t_id); + Css3::parse_stream(&mut stream, config, origin, source_url) + } - match result { - Ok(Some(node)) => Ok(node), - Ok(None) => Ok(Node::new( - NodeType::StyleSheet { - children: Vec::new(), - }, - Location::default(), - )), - Err(e) => Err(e), - } + /// Parses a direct stream to a CssStyleSheet + pub fn parse_stream( + stream: &mut ByteStream, + config: ParserConfig, + origin: CssOrigin, + source_url: &str, + ) -> CssResult { + Css3::new(stream, config, origin, source_url).parse() } - /// Create a new parser with the given bytestream - fn new(it: &'stream mut ByteStream) -> Self { - Self { - tokenizer: Tokenizer::new(it, Location::default()), - allow_values_in_argument_list: Vec::new(), - config: Default::default(), + fn parse(&mut self) -> CssResult { + if self.config.context != Context::Stylesheet { + return Err(CssError::new("Expected a stylesheet context")); } - } - /// Actual parser implementation - fn parse_internal(&mut self, config: ParserConfig) -> Result, Error> { - self.config = config; + let t_id = timing_start!("css3.parse", self.config.source.as_deref().unwrap_or("")); - match self.config.context { - Context::Stylesheet => self.parse_stylesheet(), + // let mut stream = ByteStream::new(Encoding::UTF8, None); + // stream.read_from_str(data, Some(Encoding::UTF8)); + // stream.close(); + + let node_tree = match self.config.context { + Context::Stylesheet => self.parse_stylesheet_internal(), Context::Rule => self.parse_rule(), Context::AtRule => self.parse_at_rule(true), Context::Declaration => self.parse_declaration(), + }; + + timing_stop!(t_id); + + match node_tree { + Ok(None) => Err(CssError::new("No node tree found")), + Ok(Some(node)) => convert_ast_to_stylesheet(&node, self.origin, self.source.clone().as_str()), + Err(e) => Err(e), } } } +/// Loads the default user agent stylesheet +pub fn load_default_useragent_stylesheet() -> CssStylesheet { + // @todo: we should be able to browse to gosub:useragent.css and see the actual useragent css file + let url = "gosub:useragent.css"; + + let config = ParserConfig { + ignore_errors: true, + match_values: true, + ..Default::default() + }; + + let css_data = include_str!("../resources/useragent.css"); + Css3::parse_str(css_data, config, CssOrigin::UserAgent, url).expect("Could not parse useragent stylesheet") +} + #[cfg(test)] mod tests { use super::*; - use crate::walker::Walker; + // use crate::walker::Walker; use simple_logger::SimpleLogger; #[test] @@ -109,14 +138,13 @@ mod tests { }; let css = std::fs::read_to_string(filename).unwrap(); - let res = Css3::parse(css.as_str(), config); + let res = Css3::parse_str(css.as_str(), config, CssOrigin::Author, filename); if res.is_err() { println!("{:?}", res.err().unwrap()); - return; } - let binding = res.unwrap(); - let w = Walker::new(&binding); - w.walk_stdout(); + // let binding = res.unwrap(); + // let w = Walker::new(&binding); + // w.walk_stdout(); } } diff --git a/crates/gosub_css3/src/logging.rs b/crates/gosub_css3/src/logging.rs new file mode 100644 index 000000000..a1d6032c6 --- /dev/null +++ b/crates/gosub_css3/src/logging.rs @@ -0,0 +1,528 @@ +use crate::colors::RgbColor; +use anyhow::anyhow; +use core::fmt::Debug; +use std::cmp::Ordering; +use std::fmt::Display; +use gosub_shared::byte_stream::Location; + +/// Defines a complete stylesheet with all its rules and the location where it was found +#[derive(Debug)] +pub struct CssStylesheet { + /// List of rules found in this stylesheet + pub rules: Vec, + /// Origin of the stylesheet (user agent, author, user) + pub origin: CssOrigin, + /// Url or file path where the stylesheet was found + pub location: String, + /// Any issues during parsing of the stylesheet + pub parse_log: Vec +} + +/// A CSS rule, which contains a list of selectors and a list of declarations +#[derive(Debug, PartialEq, Clone)] +pub struct CssRule { + /// Selectors that must match for the declarations to apply + pub selectors: Vec, + /// Actual declarations that will be applied if the selectors match + pub declarations: Vec, +} + +impl CssRule { + pub fn selectors(&self) -> &Vec { + &self.selectors + } + + pub fn declarations(&self) -> &Vec { + &self.declarations + } +} + +/// A CSS declaration, which contains a property, value and a flag for !important +#[derive(Debug, PartialEq, Clone)] +pub struct CssDeclaration { + // Css property color + pub property: String, + // Raw values of the declaration. It is not calculated or converted in any way (ie: "red", "50px" etc) + // There can be multiple values (ie: "1px solid black" are split into 3 values) + pub value: Vec, + // ie: !important + pub important: bool, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct CssSelector { + // List of parts that make up this selector + pub parts: Vec, +} + +impl CssSelector { + /// Generate specificity for this selector + pub fn specificity(&self) -> Specificity { + let mut id_count = 0; + let mut class_count = 0; + let mut element_count = 0; + for part in &self.parts { + match part.type_ { + CssSelectorType::Id => { + id_count += 1; + } + CssSelectorType::Class => { + class_count += 1; + } + CssSelectorType::Type => { + element_count += 1; + } + _ => {} + } + } + Specificity::new(id_count, class_count, element_count) + } +} + +/// @todo: it would be nicer to have a struct for each type of selector part, but for now we'll keep it simple +/// Represents a CSS selector part, which has a type and value (e.g. type=Class, class="my-class") +#[derive(PartialEq, Clone, Default)] +pub struct CssSelectorPart { + pub type_: CssSelectorType, + pub value: String, + pub matcher: MatcherType, + pub name: String, + pub flags: String, +} + +impl Debug for CssSelectorPart { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.type_ { + CssSelectorType::Universal => { + write!(f, "*") + } + CssSelectorType::Attribute => { + write!( + f, + "[{} {} {} {}]", + self.name, self.matcher, self.value, self.flags + ) + } + CssSelectorType::Class => { + write!(f, ".{}", self.value) + } + CssSelectorType::Id => { + write!(f, "#{}", self.value) + } + CssSelectorType::PseudoClass => { + write!(f, ":{}", self.value) + } + CssSelectorType::PseudoElement => { + write!(f, "::{}", self.value) + } + CssSelectorType::Combinator => { + write!(f, "'{}'", self.value) + } + CssSelectorType::Type => { + write!(f, "{}", self.value) + } + } + } +} + +/// Represents a CSS selector type for this part +#[derive(Debug, PartialEq, Clone, Default)] +pub enum CssSelectorType { + Universal, // '*' + #[default] + Type, // ul, a, h1, etc + Attribute, // [type ~= "text" i] (name, matcher, value, flags) + Class, // .myclass + Id, // #myid + PseudoClass, // :hover, :active + PseudoElement, // ::first-child + Combinator, +} + +/// Represents which type of matcher is used (in case of an attribute selector type) +#[derive(Default, PartialEq, Clone)] +pub enum MatcherType { + #[default] + None, // No matcher + Equals, // Equals + Includes, // Must include + DashMatch, // Must start with + PrefixMatch, // Must begin with + SuffixMatch, // Must ends with + SubstringMatch, // Must contain +} + +impl Display for MatcherType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MatcherType::None => write!(f, ""), + MatcherType::Equals => write!(f, "="), + MatcherType::Includes => write!(f, "~="), + MatcherType::DashMatch => write!(f, "|="), + MatcherType::PrefixMatch => write!(f, "^="), + MatcherType::SuffixMatch => write!(f, "$="), + MatcherType::SubstringMatch => write!(f, "*="), + } + } +} + +/// Defines the specificity for a selector +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Specificity(u32, u32, u32); + +impl Specificity { + pub fn new(a: u32, b: u32, c: u32) -> Self { + Self(a, b, c) + } +} + +impl PartialOrd for Specificity { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Specificity { + fn cmp(&self, other: &Self) -> Ordering { + match self.0.cmp(&other.0) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => match self.1.cmp(&other.1) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => match self.2.cmp(&other.2) { + Ordering::Greater => Ordering::Greater, + Ordering::Less => Ordering::Less, + Ordering::Equal => Ordering::Equal, + }, + }, + } + } +} + +/// Actual CSS value, can be a color, length, percentage, string or unit. Some relative values will be computed +/// from other values (ie: Percent(50) will convert to Length(100) when the parent width is 200) +#[derive(Debug, Clone, PartialEq)] +pub enum CssValue { + None, + Color(RgbColor), + Zero, + Number(f32), + Percentage(f32), + String(String), + Unit(f32, String), + Function(String, Vec), + Initial, + Inherit, + Comma, + List(Vec), +} + +impl Display for CssValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CssValue::None => write!(f, "none"), + CssValue::Color(col) => { + write!( + f, + "#{:02x}{:02x}{:02x}{:02x}", + col.r as u8, col.g as u8, col.b as u8, col.a as u8 + ) + } + CssValue::Zero => write!(f, "0"), + CssValue::Number(num) => write!(f, "{}", num), + CssValue::Percentage(p) => write!(f, "{}%", p), + CssValue::String(s) => write!(f, "{}", s), + CssValue::Unit(val, unit) => write!(f, "{}{}", val, unit), + CssValue::Function(name, args) => { + write!(f, "{}(", name)?; + for (i, arg) in args.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ")") + } + CssValue::Initial => write!(f, "initial"), + CssValue::Inherit => write!(f, "inherit"), + CssValue::Comma => write!(f, ","), + CssValue::List(v) => { + write!(f, "List(")?; + for (i, value) in v.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", value)?; + } + write!(f, ")") + } + } + } +} + +impl CssValue { + pub fn to_color(&self) -> Option { + match self { + CssValue::Color(col) => Some(*col), + CssValue::String(s) => Some(RgbColor::from(s.as_str())), + _ => None, + } + } + + pub fn unit_to_px(&self) -> f32 { + //TODO: Implement the rest of the units + match self { + CssValue::Unit(val, unit) => match unit.as_str() { + "px" => *val, + "em" => *val * 16.0, + "rem" => *val * 16.0, + _ => *val, + }, + CssValue::String(value) => { + if value.ends_with("px") { + value.trim_end_matches("px").parse::().unwrap() + } else if value.ends_with("rem") { + value.trim_end_matches("rem").parse::().unwrap() * 16.0 + } else if value.ends_with("em") { + value.trim_end_matches("em").parse::().unwrap() * 16.0 + } else { + 0.0 + } + } + _ => 0.0, + } + } + + /// Converts a CSS AST node to a CSS value + pub fn parse_ast_node(node: &crate::node::Node) -> CssResult { + match *node.node_type.clone() { + crate::node::NodeType::Ident { value } => Ok(CssValue::String(value)), + crate::node::NodeType::Number { value } => { + if value == 0.0 { + // Zero is a special case since we need to do some pattern matching once in a while, and + // this is not possible (anymore) with floating point 0.0 it seems + Ok(CssValue::Zero) + } else { + Ok(CssValue::Number(value)) + } + } + crate::node::NodeType::Percentage { value } => Ok(CssValue::Percentage(value)), + crate::node::NodeType::Dimension { value, unit } => Ok(CssValue::Unit(value, unit)), + crate::node::NodeType::String { value } => Ok(CssValue::String(value)), + crate::node::NodeType::Hash { value } => Ok(CssValue::String(value)), + crate::node::NodeType::Operator(_) => Ok(CssValue::None), + crate::node::NodeType::Calc { .. } => { + Ok(CssValue::Function("calc".to_string(), vec![])) + } + crate::node::NodeType::Url { url } => Ok(CssValue::Function( + "url".to_string(), + vec![CssValue::String(url)], + )), + crate::node::NodeType::Function { name, arguments } => { + let mut list = vec![]; + for node in arguments.iter() { + match CssValue::parse_ast_node(node) { + Ok(value) => list.push(value), + Err(e) => return Err(e), + } + } + Ok(CssValue::Function(name, list)) + } + _ => Err(anyhow!(format!( + "Cannot convert node to CssValue: {:?}", + node + ))), + } + } + + /// Parses a string into a CSS value or list of css values + pub fn parse_str(value: &str) -> CssResult { + match value { + "initial" => return Ok(CssValue::Initial), + "inherit" => return Ok(CssValue::Inherit), + "none" => return Ok(CssValue::None), + "" => return Ok(CssValue::String("".into())), + _ => {} + } + + if let Ok(num) = value.parse::() { + return Ok(CssValue::Number(num)); + } + + // Color values + if value.starts_with("color(") && value.ends_with(')') { + return Ok(CssValue::Color(RgbColor::from( + value[6..value.len() - 1].to_string().as_str(), + ))); + } + + // Percentages + if value.ends_with('%') { + if let Ok(num) = value[0..value.len() - 1].parse::() { + return Ok(CssValue::Percentage(num)); + } + } + + // units. If the value starts with a number and ends with some non-numerical + let mut split_index = None; + for (index, char) in value.chars().enumerate() { + if char.is_alphabetic() { + split_index = Some(index); + break; + } + } + if let Some(index) = split_index { + let (number_part, unit_part) = value.split_at(index); + if let Ok(number) = number_part.parse::() { + return Ok(CssValue::Unit(number, unit_part.to_string())); + } + } + + Ok(CssValue::String(value.to_string())) + } +} + +#[cfg(test)] +mod test { + use super::*; + + // #[test] + // fn test_css_value_to_color() { + // assert_eq!(CssValue::from_str("color(#ff0000)").unwrap().to_color().unwrap(), RgbColor::from("#ff0000")); + // assert_eq!(CssValue::from_str("'Hello'").unwrap().to_color().unwrap(), RgbColor::from("#000000")); + // } + // + // #[test] + // fn test_css_value_unit_to_px() { + // assert_eq!(CssValue::from_str("10px").unwrap().unit_to_px(), 10.0); + // assert_eq!(CssValue::from_str("10em").unwrap().unit_to_px(), 160.0); + // assert_eq!(CssValue::from_str("10rem").unwrap().unit_to_px(), 160.0); + // assert_eq!(CssValue::from_str("10").unwrap().unit_to_px(), 0.0); + // } + + #[test] + fn test_css_rule() { + let rule = CssRule { + selectors: vec![CssSelector { + parts: vec![CssSelectorPart { + type_: CssSelectorType::Type, + value: "h1".to_string(), + ..Default::default() + }], + }], + declarations: vec![CssDeclaration { + property: "color".to_string(), + value: vec![CssValue::String("red".to_string())], + important: false, + }], + }; + + assert_eq!(rule.selectors().len(), 1); + assert_eq!( + rule.selectors() + .first() + .unwrap() + .parts + .first() + .unwrap() + .value, + "h1" + ); + assert_eq!(rule.declarations().len(), 1); + assert_eq!(rule.declarations().first().unwrap().property, "color"); + } + + #[test] + fn test_specificity() { + let selector = CssSelector { + parts: vec![ + CssSelectorPart { + type_: CssSelectorType::Type, + value: "h1".to_string(), + ..Default::default() + }, + CssSelectorPart { + type_: CssSelectorType::Class, + value: "myclass".to_string(), + ..Default::default() + }, + CssSelectorPart { + type_: CssSelectorType::Id, + value: "myid".to_string(), + ..Default::default() + }, + ], + }; + + let specificity = selector.specificity(); + assert_eq!(specificity, Specificity::new(1, 1, 1)); + + let selector = CssSelector { + parts: vec![ + CssSelectorPart { + type_: CssSelectorType::Type, + value: "h1".to_string(), + ..Default::default() + }, + CssSelectorPart { + type_: CssSelectorType::Class, + value: "myclass".to_string(), + ..Default::default() + }, + ], + }; + + let specificity = selector.specificity(); + assert_eq!(specificity, Specificity::new(0, 1, 1)); + + let selector = CssSelector { + parts: vec![CssSelectorPart { + type_: CssSelectorType::Type, + value: "h1".to_string(), + ..Default::default() + }], + }; + + let specificity = selector.specificity(); + assert_eq!(specificity, Specificity::new(0, 0, 1)); + + let selector = CssSelector { + parts: vec![ + CssSelectorPart { + type_: CssSelectorType::Class, + value: "myclass".to_string(), + ..Default::default() + }, + CssSelectorPart { + type_: CssSelectorType::Class, + value: "otherclass".to_string(), + ..Default::default() + }, + ], + }; + + let specificity = selector.specificity(); + assert_eq!(specificity, Specificity::new(0, 2, 0)); + } + + #[test] + fn test_specificity_ordering() { + let specificity1 = Specificity::new(1, 1, 1); + let specificity2 = Specificity::new(0, 1, 1); + let specificity3 = Specificity::new(0, 0, 1); + let specificity4 = Specificity::new(0, 2, 0); + let specificity5 = Specificity::new(1, 0, 0); + let specificity6 = Specificity::new(1, 2, 1); + let specificity7 = Specificity::new(1, 1, 2); + let specificity8 = Specificity::new(2, 1, 1); + + assert!(specificity1 > specificity2); + assert!(specificity2 > specificity3); + assert!(specificity3 < specificity4); + assert!(specificity4 < specificity5); + assert!(specificity5 < specificity6); + assert!(specificity6 > specificity7); + assert!(specificity7 < specificity8); + } +} diff --git a/crates/gosub_css3/src/matcher.rs b/crates/gosub_css3/src/matcher.rs new file mode 100644 index 000000000..299733b55 --- /dev/null +++ b/crates/gosub_css3/src/matcher.rs @@ -0,0 +1,6 @@ +pub mod property_definitions; +pub mod shorthands; +pub mod styling; +mod syntax; +mod syntax_matcher; +mod walker; diff --git a/crates/gosub_styling/src/property_definitions.rs b/crates/gosub_css3/src/matcher/property_definitions.rs similarity index 92% rename from crates/gosub_styling/src/property_definitions.rs rename to crates/gosub_css3/src/matcher/property_definitions.rs index 2ed361366..41fde9c71 100644 --- a/crates/gosub_styling/src/property_definitions.rs +++ b/crates/gosub_css3/src/matcher/property_definitions.rs @@ -1,15 +1,13 @@ use std::collections::HashMap; +use std::sync::LazyLock; use log::warn; -use gosub_css3::stylesheet::CssValue; - -use crate::shorthands::{FixList, Shorthands}; -use std::sync::LazyLock; - -use crate::syntax::GroupCombinators::Juxtaposition; -use crate::syntax::{CssSyntax, SyntaxComponent}; -use crate::syntax_matcher::CssSyntaxTree; +use crate::matcher::shorthands::{FixList, Shorthands}; +use crate::matcher::syntax::GroupCombinators::Juxtaposition; +use crate::matcher::syntax::{CssSyntax, SyntaxComponent}; +use crate::matcher::syntax_matcher::CssSyntaxTree; +use crate::stylesheet::CssValue; /// List of elements that are built-in data types in the CSS specification. These will be handled /// by the syntax matcher as built-in types. @@ -240,23 +238,16 @@ impl CssDefinitions { } /// Resolve a syntax component - pub fn resolve_component( - &mut self, - component: &SyntaxComponent, - prop_name: &str, - ) -> SyntaxComponent { + pub fn resolve_component(&mut self, component: &SyntaxComponent, prop_name: &str) -> SyntaxComponent { match component { SyntaxComponent::Definition { - datatype, - multipliers, - .. + datatype, multipliers, .. } => { // First step: Resolve by looking the definition up in the syntax defintions. if let Some(syntax_element) = self.syntax.get(datatype) { let mut syntax_element = syntax_element.clone(); if !syntax_element.resolved { - syntax_element.syntax = - self.resolve_syntax(&syntax_element.syntax, prop_name); + syntax_element.syntax = self.resolve_syntax(&syntax_element.syntax, prop_name); syntax_element.resolved = true; self.syntax.insert(datatype.clone(), syntax_element.clone()); } @@ -348,21 +339,18 @@ impl CssDefinitions { } } -pub static CSS_DEFINITIONS: LazyLock CssDefinitions> = - LazyLock::new(pars_definition_files); +pub static CSS_DEFINITIONS: LazyLock CssDefinitions> = LazyLock::new(pars_definition_files); /// Parses the internal CSS definition file fn pars_definition_files() -> CssDefinitions { // parse all syntax, so we can use them in the properties - let contents = include_str!("../resources/definitions/definitions_values.json"); - let json: serde_json::Value = - serde_json::from_str(contents).expect("JSON was not well-formatted"); + let contents = include_str!("../../resources/definitions/definitions_values.json"); + let json: serde_json::Value = serde_json::from_str(contents).expect("JSON was not well-formatted"); let syntax = parse_syntax_file(json); // Parse property definitions - let contents = include_str!("../resources/definitions/definitions_properties.json"); - let json: serde_json::Value = - serde_json::from_str(contents).expect("JSON was not well-formatted"); + let contents = include_str!("../../resources/definitions/definitions_properties.json"); + let json: serde_json::Value = serde_json::from_str(contents).expect("JSON was not well-formatted"); let properties = parse_property_file(json); // Create definition structure, and resolve all definitions @@ -503,9 +491,8 @@ fn parse_property_file(json: serde_json::Value) -> HashMap { @@ -566,9 +553,7 @@ mod tests { .matches(&[str!("solid"), CssValue::Color(RgbColor::from("black")),])); assert_true!(prop.clone().matches(&[str!("solid")])); assert_false!(prop.clone().matches(&[str!("not-solid")])); - assert_false!(prop - .clone() - .matches(&[str!("solid"), str!("solid"), unit!(1.0, "px"),])); + assert_false!(prop.clone().matches(&[str!("solid"), str!("solid"), unit!(1.0, "px"),])); } #[test] @@ -579,9 +564,7 @@ mod tests { PropertyDefinition { name: "color".to_string(), computed: vec![], - syntax: CssSyntax::new("color()") - .compile() - .expect("Could not compile syntax"), + syntax: CssSyntax::new("color()").compile().expect("Could not compile syntax"), inherited: false, initial_value: None, resolved: false, @@ -604,9 +587,7 @@ mod tests { "border-bottom-style".to_string(), "border-left-style".to_string(), ], - syntax: CssSyntax::new("") - .compile() - .expect("Could not compile syntax"), + syntax: CssSyntax::new("").compile().expect("Could not compile syntax"), inherited: false, initial_value: Some(str!("thick".to_string())), resolved: false, @@ -669,9 +650,7 @@ mod tests { assert_true!(def.clone().matches(&[str!("Menu")])); assert_true!(def.clone().matches(&[str!("blue")])); - assert_true!(def - .clone() - .matches(&[CssValue::Color(RgbColor::from("#ff0000"))])); + assert_true!(def.clone().matches(&[CssValue::Color(RgbColor::from("#ff0000"))])); assert_true!(def.clone().matches(&[str!("rebeccapurple")])); assert_false!(def.clone().matches(&[str!("thiscolordoesnotexist")])); @@ -700,12 +679,9 @@ mod tests { assert_true!(def.clone().matches(&[str!("left"), unit!(10.0, "px"),])); // background-position: left 10px top 20px; - assert_true!(def.clone().matches(&[ - str!("left"), - unit!(10.0, "px"), - str!("top"), - unit!(20.0, "px"), - ])); + assert_true!(def + .clone() + .matches(&[str!("left"), unit!(10.0, "px"), str!("top"), unit!(20.0, "px"),])); // background-position: right 15% bottom 5%; assert_true!(def.clone().matches(&[ @@ -727,17 +703,12 @@ mod tests { assert_true!(def.clone().matches(&[CssValue::Percentage(75.0),])); // background-position: top 10px center; - assert_true!(def - .clone() - .matches(&[str!("top"), unit!(10.0, "px"), str!("center"),])); + assert_true!(def.clone().matches(&[str!("top"), unit!(10.0, "px"), str!("center"),])); // background-position: bottom 20px right 30px; - assert_true!(def.clone().matches(&[ - str!("bottom"), - unit!(20.0, "px"), - str!("right"), - unit!(30.0, "px"), - ])); + assert_true!(def + .clone() + .matches(&[str!("bottom"), unit!(20.0, "px"), str!("right"), unit!(30.0, "px"),])); // background-position: 20% 80%; assert_true!(def @@ -758,9 +729,7 @@ mod tests { ])); // background-position: center top 35px; - assert_true!(def - .clone() - .matches(&[str!("center"), str!("top"), unit!(35.0, "px"),])); + assert_true!(def.clone().matches(&[str!("center"), str!("top"), unit!(35.0, "px"),])); // background-position: left 45% bottom 25%; assert_true!(def.clone().matches(&[ @@ -885,11 +854,8 @@ mod tests { .clone() .matches(&[unit!(1.0, "px"), unit!(2.0, "px"), unit!(3.0, "px")])); - assert!(def.clone().matches(&[ - unit!(1.0, "px"), - unit!(2.0, "px"), - unit!(3.0, "px"), - unit!(4.0, "px"), - ])); + assert!(def + .clone() + .matches(&[unit!(1.0, "px"), unit!(2.0, "px"), unit!(3.0, "px"), unit!(4.0, "px"),])); } } diff --git a/crates/gosub_styling/src/shorthands.rs b/crates/gosub_css3/src/matcher/shorthands.rs similarity index 92% rename from crates/gosub_styling/src/shorthands.rs rename to crates/gosub_css3/src/matcher/shorthands.rs index 81386e8e3..38a678f9c 100644 --- a/crates/gosub_styling/src/shorthands.rs +++ b/crates/gosub_css3/src/matcher/shorthands.rs @@ -1,11 +1,11 @@ +use crate::stylesheet::{CssValue, Specificity}; +use gosub_shared::traits::css3::CssOrigin; use std::collections::hash_map::Entry; -use gosub_css3::stylesheet::{CssOrigin, CssValue, Specificity}; - -use crate::property_definitions::CssDefinitions; -use crate::styling::{CssProperties, CssProperty, DeclarationProperty}; -use crate::syntax::{SyntaxComponent, SyntaxComponentMultiplier}; -use crate::syntax_matcher::CssSyntaxTree; +use crate::matcher::property_definitions::CssDefinitions; +use crate::matcher::styling::{CssProperties, CssProperty, DeclarationProperty}; +use crate::matcher::syntax::{SyntaxComponent, SyntaxComponentMultiplier}; +use crate::matcher::syntax_matcher::CssSyntaxTree; impl CssSyntaxTree { pub fn has_property_syntax(&self, property: &str) -> Option { @@ -28,9 +28,7 @@ impl SyntaxComponent { pub fn has_property_syntax(&self, prop: &str, path: &mut Vec) -> bool { match self { SyntaxComponent::Property { property, .. } => prop == property, - SyntaxComponent::Definition { - datatype, quoted, .. - } if *quoted => prop == datatype, + SyntaxComponent::Definition { datatype, quoted, .. } if *quoted => prop == datatype, SyntaxComponent::Group { components, .. } => { for (i, component) in components.iter().enumerate() { path.push(i); @@ -218,11 +216,7 @@ impl<'a> ShorthandResolver<'a> { } if !complete.is_empty() { - let idx = self - .fix_list - .multipliers - .iter_mut() - .find(|m| m.0 == self.name); + let idx = self.fix_list.multipliers.iter_mut().find(|m| m.0 == self.name); if let Some(idx) = idx { let Some(items) = self.multiplier.get_names(complete, idx.1) else { @@ -431,12 +425,7 @@ impl CssDefinitions { } } - pub fn resolve_shorthands( - &self, - computed: &[String], - syntax: &CssSyntaxTree, - name: &str, - ) -> Option { + pub fn resolve_shorthands(&self, computed: &[String], syntax: &CssSyntaxTree, name: &str) -> Option { if computed.len() <= 1 || syntax.components.is_empty() { return None; } @@ -548,9 +537,7 @@ impl CssDefinitions { match component { SyntaxComponent::Definition { datatype, .. } => { if let Some(d) = self.syntax.get(datatype) { - if let Some(mut shorthands) = - self.resolve_shorthands(computed, &d.syntax, name) - { + if let Some(mut shorthands) = self.resolve_shorthands(computed, &d.syntax, name) { shorthands.multiplier = Multiplier::None; return Some(shorthands); @@ -559,9 +546,7 @@ impl CssDefinitions { if let Some(p) = self.properties.get(datatype) { //currently properties get parsed as definitions - if let Some(mut shorthands) = - self.resolve_shorthands(computed, &p.syntax, name) - { + if let Some(mut shorthands) = self.resolve_shorthands(computed, &p.syntax, name) { shorthands.multiplier = Multiplier::None; return Some(shorthands); @@ -571,9 +556,7 @@ impl CssDefinitions { SyntaxComponent::Property { property, .. } => { if let Some(d) = self.properties.get(property) { - if let Some(mut shorthands) = - self.resolve_shorthands(computed, &d.syntax, name) - { + if let Some(mut shorthands) = self.resolve_shorthands(computed, &d.syntax, name) { shorthands.multiplier = Multiplier::None; return Some(shorthands); @@ -603,11 +586,10 @@ impl CssDefinitions { #[cfg(test)] mod tests { - use gosub_css3::colors::RgbColor; - use gosub_css3::stylesheet::CssValue; - - use crate::property_definitions::get_css_definitions; - use crate::shorthands::FixList; + use crate::colors::RgbColor; + use crate::matcher::property_definitions::get_css_definitions; + use crate::matcher::shorthands::CssValue; + use crate::matcher::shorthands::FixList; macro_rules! str { ($s:expr) => { @@ -666,10 +648,9 @@ mod tests { ); fix_list = FixList::new(); - assert!(prop.clone().matches_and_shorthands( - &[unit!(1.0, "px"), unit!(2.0, "px"), unit!(3.0, "px"),], - &mut fix_list, - )); + assert!(prop + .clone() + .matches_and_shorthands(&[unit!(1.0, "px"), unit!(2.0, "px"), unit!(3.0, "px"),], &mut fix_list,)); assert_eq!( fix_list, @@ -686,12 +667,7 @@ mod tests { fix_list = FixList::new(); assert!(prop.clone().matches_and_shorthands( - &[ - unit!(1.0, "px"), - unit!(2.0, "px"), - unit!(3.0, "px"), - unit!(4.0, "px"), - ], + &[unit!(1.0, "px"), unit!(2.0, "px"), unit!(3.0, "px"), unit!(4.0, "px"),], &mut fix_list, )); @@ -737,10 +713,7 @@ mod tests { fix_list = FixList::new(); assert!(prop.clone().matches_and_shorthands( - &[ - str!("solid"), - CssValue::Color(RgbColor::new(0.0, 0.0, 0.0, 0.0)) - ], + &[str!("solid"), CssValue::Color(RgbColor::new(0.0, 0.0, 0.0, 0.0))], &mut fix_list, )); diff --git a/crates/gosub_styling/src/styling.rs b/crates/gosub_css3/src/matcher/styling.rs similarity index 78% rename from crates/gosub_styling/src/styling.rs rename to crates/gosub_css3/src/matcher/styling.rs index 631165256..0a1c46004 100644 --- a/crates/gosub_styling/src/styling.rs +++ b/crates/gosub_css3/src/matcher/styling.rs @@ -1,17 +1,21 @@ -use crate::property_definitions::{get_css_definitions, CSS_DEFINITIONS}; use core::fmt::Debug; -use gosub_css3::stylesheet::{ - Combinator, CssOrigin, CssSelector, CssSelectorPart, CssValue, MatcherType, Specificity, -}; -use gosub_html5::node::{Node, NodeId}; -use gosub_html5::parser::document::{Document, DocumentHandle}; +use gosub_shared::document::DocumentHandle; +use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::{CssOrigin, CssPropertyMap, CssSystem}; +use gosub_shared::traits::document::Document; +use gosub_shared::traits::node::ClassList; +use gosub_shared::traits::node::ElementDataType; +use gosub_shared::traits::node::Node; use itertools::Itertools; use std::cmp::Ordering; use std::collections::HashMap; +use crate::matcher::property_definitions::get_css_definitions; +use crate::stylesheet::{Combinator, CssSelector, CssSelectorPart, CssValue, MatcherType, Specificity}; + // Matches a complete selector (all parts) against the given node(id) -pub(crate) fn match_selector( - document: DocumentHandle, +pub(crate) fn match_selector, C: CssSystem>( + document: DocumentHandle, node_id: NodeId, selector: &CssSelector, ) -> (bool, Specificity) { @@ -35,13 +39,13 @@ fn consume<'a, T>(this: &mut &'a [T]) -> Option<&'a T> { } /// Returns true when the given node matches the part(s) -fn match_selector_parts( - document: DocumentHandle, +fn match_selector_parts, C: CssSystem>( + handle: DocumentHandle, node_id: NodeId, mut parts: &[CssSelectorPart], ) -> bool { - let binding = document.get(); - let mut next_current_node = binding.get_node_by_id(node_id); + let binding = handle.get(); + let mut next_current_node = binding.node_by_id(node_id); if next_current_node.is_none() { return false; } @@ -55,13 +59,7 @@ fn match_selector_parts( return false; } - if !match_selector_part( - part, - current_node, - &binding, - &mut next_current_node, - &mut parts, - ) { + if !match_selector_part(part, current_node, &*binding, &mut next_current_node, &mut parts) { return false; } @@ -74,11 +72,11 @@ fn match_selector_parts( true } -fn match_selector_part<'a>( +fn match_selector_part<'a, D: Document, C: CssSystem>( part: &CssSelectorPart, - current_node: &Node, - doc: &'a Document, - next_node: &mut Option<&'a Node>, + current_node: &D::Node, + doc: &'a D, + next_node: &mut Option<&'a D::Node>, parts: &mut &[CssSelectorPart], ) -> bool { match part { @@ -87,24 +85,25 @@ fn match_selector_part<'a>( true } CssSelectorPart::Type(name) => { - if !current_node.is_element() { + if !current_node.is_element_node() { return false; } - *name == current_node.as_element().name + *name == current_node.get_element_data().unwrap().name() } CssSelectorPart::Class(name) => { - if !current_node.is_element() { + if !current_node.is_element_node() { return false; } - current_node.as_element().classes.contains(name) + current_node.get_element_data().unwrap().classlist().contains(name) } CssSelectorPart::Id(name) => { - if !current_node.is_element() { + if !current_node.is_element_node() { return false; } current_node - .as_element() - .attributes + .get_element_data() + .unwrap() + .attributes() .get("id") .unwrap_or(&"".to_string()) == name @@ -112,15 +111,17 @@ fn match_selector_part<'a>( CssSelectorPart::Attribute(attr) => { let wanted_attr_name = &attr.name; - if !current_node.has_attribute(wanted_attr_name) { + if current_node.get_element_data().is_none() { + return false; + } + + let element_data = current_node.get_element_data().unwrap(); + if !element_data.attributes().contains_key(wanted_attr_name) { return false; } let mut wanted_attr_value = &attr.value; - let mut got_attr_value = current_node - .get_attribute(wanted_attr_name) - .map(|v| v.as_str()) - .unwrap_or(""); + let mut got_attr_value = element_data.attributes().get(wanted_attr_name).unwrap(); let mut _wanted_buf = String::new(); //Two buffers, so we don't need to clone the value if we match case-sensitive let mut _got_buf = String::new(); @@ -144,9 +145,7 @@ fn match_selector_part<'a>( } MatcherType::Includes => { // Contains word - wanted_attr_value - .split_whitespace() - .any(|s| s == got_attr_value) + wanted_attr_value.split_whitespace().any(|s| s == got_attr_value) } MatcherType::DashMatch => { // Exact value or value followed by a hyphen @@ -179,18 +178,19 @@ fn match_selector_part<'a>( // We don't have the descendant combinator (space), as this is the default behaviour match combinator { Combinator::Descendant => { - let Some(mut parent_id) = current_node.parent else { + // let handle_clone = handle.clone(); + + let Some(mut parent_id) = current_node.parent_id() else { return false; }; let last = consume(parts); - let Some(last) = last else { return false; }; loop { - let Some(parent) = doc.get_node_by_id(parent_id) else { + let Some(parent) = doc.node_by_id(parent_id) else { return false; }; @@ -200,7 +200,7 @@ fn match_selector_part<'a>( return true; } - let Some(p) = parent.parent else { + let Some(p) = parent.parent_id() else { return false; }; @@ -209,7 +209,7 @@ fn match_selector_part<'a>( } Combinator::Child => { // Child combinator. Only matches the direct child - let Some(parent) = current_node.parent else { + let Some(parent) = current_node.parent_id() else { return false; }; @@ -219,7 +219,7 @@ fn match_selector_part<'a>( return false; }; - let Some(parent) = doc.get_node_by_id(parent) else { + let Some(parent) = doc.node_by_id(parent) else { return false; }; @@ -228,13 +228,14 @@ fn match_selector_part<'a>( match_selector_part(last, parent, doc, next_node, parts) } Combinator::NextSibling => { - let Some(children) = doc.parent_node(current_node).map(|p| &p.children) else { + let parent_node = doc.node_by_id(current_node.parent_id().unwrap()); + let Some(children) = parent_node.map(|p| p.children()) else { return false; }; let Some(my_index) = children .iter() - .find_position(|c| **c == current_node.id) + .find_position(|c| **c == current_node.id()) .map(|(i, _)| i) else { return false; @@ -252,7 +253,7 @@ fn match_selector_part<'a>( return false; }; - let Some(prev) = doc.get_node_by_id(prev_id) else { + let Some(prev) = doc.node_by_id(prev_id) else { return false; }; @@ -261,7 +262,8 @@ fn match_selector_part<'a>( match_selector_part(last, prev, doc, next_node, parts) } Combinator::SubsequentSibling => { - let Some(children) = doc.parent_node(current_node).map(|p| &p.children) else { + let parent_node = doc.node_by_id(current_node.parent_id().unwrap()); + let Some(children) = parent_node.map(|p| p.children()) else { return false; }; @@ -270,11 +272,11 @@ fn match_selector_part<'a>( }; for child in children { - if *child == current_node.id { + if *child == current_node.id() { break; } - let Some(child) = doc.get_node_by_id(*child) else { + let Some(child) = doc.node_by_id(*child) else { continue; }; @@ -289,10 +291,6 @@ fn match_selector_part<'a>( let namespace = consume(parts); let Some(namespace) = namespace else { - if current_node.namespace.is_none() { - return true; - } - return false; }; @@ -304,7 +302,7 @@ fn match_selector_part<'a>( return false; }; - current_node.is_namespace(namespace) + current_node.get_element_data().unwrap().is_namespace(namespace) } Combinator::Column => { //TODO @@ -326,6 +324,7 @@ pub struct DeclarationProperty { pub origin: CssOrigin, /// Whether the declaration is !important pub important: bool, + // @TODO: location should be a Location /// The location of the declaration in the stylesheet (name.css:123) or empty pub location: String, /// The specificity of the selector that declared this property @@ -492,7 +491,7 @@ impl CssProperty { } } - // /// Returns true if the given property is a shorthand property (ie: border, margin etc) + // /// Returns true if the given property is a shorthand property (ie: border, margin etc.) pub fn is_shorthand(&self) -> bool { let defs = get_css_definitions(); match defs.find_property(&self.name) { @@ -520,8 +519,74 @@ impl CssProperty { // // Returns the initial value for the property, if any fn get_initial_value(&self) -> Option { let defs = get_css_definitions(); - defs.find_property(&self.name) - .map(|def| def.initial_value()) + defs.find_property(&self.name).map(|def| def.initial_value()) + } +} + +impl gosub_shared::traits::css3::CssProperty for CssProperty { + type Value = CssValue; + + fn compute_value(&mut self) { + self.compute_value(); + } + fn unit_to_px(&self) -> f32 { + self.actual.unit_to_px() + } + + fn as_string(&self) -> Option<&str> { + if let CssValue::String(str) = &self.actual { + Some(str) + } else { + None + } + } + + fn as_percentage(&self) -> Option { + if let CssValue::Percentage(percent) = &self.actual { + Some(*percent) + } else { + None + } + } + + fn as_unit(&self) -> Option<(f32, &str)> { + if let CssValue::Unit(value, unit) = &self.actual { + Some((*value, unit)) + } else { + None + } + } + + fn as_color(&self) -> Option<(f32, f32, f32, f32)> { + if let CssValue::Color(color) = &self.actual { + Some((color.r, color.g, color.b, color.a)) + } else { + None + } + } + + fn parse_color(&self) -> Option<(f32, f32, f32, f32)> { + self.actual.to_color().map(|color| (color.r, color.g, color.b, color.a)) + } + + fn as_number(&self) -> Option { + if let CssValue::Number(num) = &self.actual { + Some(*num) + } else { + None + } + } + + fn as_list(&self) -> Option> { + if let CssValue::List(list) = &self.actual { + Some(list.to_vec()) + } else { + None + } + } + + fn is_none(&self) -> bool { + matches!(self.actual, CssValue::None) } } @@ -530,6 +595,7 @@ impl CssProperty { #[derive(Debug)] pub struct CssProperties { pub properties: HashMap, + pub dirty: bool, } impl Default for CssProperties { @@ -542,6 +608,7 @@ impl CssProperties { pub fn new() -> Self { Self { properties: HashMap::new(), + dirty: true, } } @@ -550,17 +617,47 @@ impl CssProperties { } } -pub fn prop_is_inherit(name: &str) -> bool { - CSS_DEFINITIONS - .find_property(name) - .map(|def| def.inherited) - .unwrap_or(false) +impl CssPropertyMap for CssProperties { + type Property = CssProperty; + + fn insert_inherited(&mut self, name: &str, value: Self::Property) { + self.properties.entry(name.to_string()).or_insert(value); + } + + fn get(&self, name: &str) -> Option<&Self::Property> { + self.properties.get(name) + } + + fn get_mut(&mut self, name: &str) -> Option<&mut Self::Property> { + self.properties.get_mut(name) + } + + fn make_dirty(&mut self) { + self.dirty = true; + } + + fn iter(&self) -> impl Iterator + '_ { + self.properties.iter().map(|(k, v)| (k.as_str(), v)) + } + + fn iter_mut(&mut self) -> impl Iterator + '_ { + self.properties.iter_mut().map(|(k, v)| (k.as_str(), v)) + } + + fn make_clean(&mut self) { + self.dirty = false; + } + + fn is_dirty(&self) -> bool { + self.dirty + } } #[cfg(test)] mod tests { use super::*; - use gosub_css3::colors::RgbColor; + use crate::colors::RgbColor; + use crate::system::prop_is_inherit; #[test] fn css_props() { diff --git a/crates/gosub_styling/src/syntax.rs b/crates/gosub_css3/src/matcher/syntax.rs similarity index 94% rename from crates/gosub_styling/src/syntax.rs rename to crates/gosub_css3/src/matcher/syntax.rs index 1402f1b02..f9e732dfd 100644 --- a/crates/gosub_styling/src/syntax.rs +++ b/crates/gosub_css3/src/matcher/syntax.rs @@ -1,10 +1,9 @@ use std::fmt::{Debug, Display, Formatter}; +use gosub_shared::errors::{CssError, CssResult}; use nom::branch::alt; use nom::bytes::complete::{tag, tag_no_case, take_while}; -use nom::character::complete::{ - alpha1, alphanumeric1, char, digit0, digit1, multispace0, one_of, space0, -}; +use nom::character::complete::{alpha1, alphanumeric1, char, digit0, digit1, multispace0, one_of, space0}; use nom::combinator::{map, map_res, opt, recognize}; use nom::multi::{fold_many1, many0, many1, separated_list0, separated_list1}; use nom::number::complete::float; @@ -12,11 +11,8 @@ use nom::sequence::{delimited, pair, preceded, separated_pair, tuple}; use nom::Err; use nom::IResult; -use gosub_css3::stylesheet::CssValue; -use gosub_shared::types::Result; - -use crate::errors::Error; -use crate::syntax_matcher::CssSyntaxTree; +use crate::matcher::syntax_matcher::CssSyntaxTree; +use crate::stylesheet::CssValue; // When debugging the parser, it's nice to have some additional information ready. This should maybe // be inside a cfg setting, but for now (un)commenting the appropriate line is good enough. @@ -33,6 +29,7 @@ pub struct Group { pub components: Vec, } +#[allow(dead_code)] #[derive(PartialEq, Debug, Clone)] pub enum GroupCombinators { /// All elements must be matched in order (space delimited) @@ -259,7 +256,7 @@ impl CssSyntax { } /// Compiles the current syntax into a list of components or Err on compilation error - pub fn compile(self) -> Result { + pub fn compile(self) -> CssResult { if self.source.is_empty() { return Ok(CssSyntaxTree::new(vec![])); } @@ -268,15 +265,13 @@ impl CssSyntax { match p { Ok((input, components)) => { if !input.trim().is_empty() { - return Err(Error::CssCompile(format!( - "Failed to parse all input (left: '{}')", - input - )) - .into()); + return Err(CssError::new( + format!("Failed to parse all input (left: '{}')", input).as_str(), + )); } Ok(CssSyntaxTree::new(vec![components])) } - Err(err) => Err(Error::CssCompile(err.to_string()).into()), + Err(e) => Err(CssError::new(e.to_string().as_str())), } } } @@ -297,10 +292,7 @@ fn parse_unit(input: &str) -> IResult<&str, SyntaxComponent> { }, )) } else { - Err(Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Alpha, - ))) + Err(Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Alpha))) }; } @@ -353,10 +345,7 @@ fn parse_comma_separated_multiplier(input: &str) -> IResult<&str, SyntaxComponen )); let (input, minmax) = alt(( - map( - delimited(ws(tag("#{")), range, ws(tag("}"))), - |(min, max)| (min, max), - ), + map(delimited(ws(tag("#{")), range, ws(tag("}"))), |(min, max)| (min, max)), // No range means one or more values map(ws(tag("#")), |_| (1, u32::MAX)), ))(input)?; @@ -391,8 +380,7 @@ fn parse_multipliers(input: &str) -> IResult<&str, Vec IResult<&str, SyntaxComponent> { debug_print!("Parsing group: {}", input); - let (input, components) = - delimited(ws(tag("[")), parse_component_singlebar_list, ws(tag("]")))(input)?; + let (input, components) = delimited(ws(tag("[")), parse_component_singlebar_list, ws(tag("]")))(input)?; Ok((input, components)) } @@ -420,8 +408,7 @@ fn parse_component_singlebar_list(input: &str) -> IResult<&str, SyntaxComponent> fn parse_component_doublebar_list(input: &str) -> IResult<&str, SyntaxComponent> { debug_print!("Parsing component doublebar list: {}", input); - let (input, components) = - separated_list1(ws(tag("||")), parse_component_doubleampersand_list)(input)?; + let (input, components) = separated_list1(ws(tag("||")), parse_component_doubleampersand_list)(input)?; if components.len() == 1 { return Ok((input, components[0].clone())); @@ -441,8 +428,7 @@ fn parse_component_doublebar_list(input: &str) -> IResult<&str, SyntaxComponent> fn parse_component_doubleampersand_list(input: &str) -> IResult<&str, SyntaxComponent> { debug_print!("Parsing component doubleampersand list: {}", input); - let (input, components) = - separated_list1(ws(tag("&&")), parse_component_juxtaposition_list)(input)?; + let (input, components) = separated_list1(ws(tag("&&")), parse_component_juxtaposition_list)(input)?; if components.len() == 1 { return Ok((input, components[0].clone())); @@ -597,11 +583,7 @@ fn parse_function(input: &str) -> IResult<&str, SyntaxComponent> { space0, tuple((space0, char(')'), space0)), ); - let arglist = delimited( - ws(tag("(")), - ws(parse_component_singlebar_list), - ws(tag(")")), - ); + let arglist = delimited(ws(tag("(")), ws(parse_component_singlebar_list), ws(tag(")"))); let (input, name) = parse_keyword(input)?; let (input, arglist) = alt((map(empty_arglist, |_| None), map(arglist, Some)))(input)?; @@ -662,21 +644,18 @@ fn parse_infinity(input: &str) -> IResult<&str, NumberOrInfinity> { /// Parses an integer (signed or unsigned) and returns NumberOrInfinity::FiniteI64, or errors when the integer is invalid fn parse_signed_integer(input: &str) -> IResult<&str, NumberOrInfinity> { - map_res( - pair(opt(char('-')), digit1), - |(sign, digits): (Option, &str)| { - let neg_multiplier = if sign == Some('-') { -1 } else { 1 }; - let num = digits.parse::().map(|num| num * neg_multiplier); - if let Ok(num) = num { - Ok(NumberOrInfinity::FiniteI64(num)) - } else { - Err(nom::Err::Error(nom::error::Error::new( - input, - nom::error::ErrorKind::Digit, - ))) - } - }, - )(input) + map_res(pair(opt(char('-')), digit1), |(sign, digits): (Option, &str)| { + let neg_multiplier = if sign == Some('-') { -1 } else { 1 }; + let num = digits.parse::().map(|num| num * neg_multiplier); + if let Ok(num) = num { + Ok(NumberOrInfinity::FiniteI64(num)) + } else { + Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::Digit, + ))) + } + })(input) } fn parse_unit_range(input: &str) -> IResult<&str, NumberOrInfinity> { @@ -702,17 +681,9 @@ fn parse_unit_range(input: &str) -> IResult<&str, NumberOrInfinity> { /// Parses a range for a type definition (ie: the square bracket part of: ) fn datatype_range(input: &str) -> IResult<&str, RangeType> { let range = separated_pair( - opt(ws(alt(( - parse_infinity, - parse_unit_range, - parse_signed_integer, - )))), + opt(ws(alt((parse_infinity, parse_unit_range, parse_signed_integer)))), tag(","), - opt(ws(alt(( - parse_infinity, - parse_unit_range, - parse_signed_integer, - )))), + opt(ws(alt((parse_infinity, parse_unit_range, parse_signed_integer)))), ); let range = map(range, |(min, max)| RangeType { @@ -735,10 +706,9 @@ fn parse_datatype(input: &str) -> IResult<&str, SyntaxComponent> { let (input, (name, quoted, range)) = delimited( ws(tag("<")), alt(( - map( - pair(keyword_or_function, opt(datatype_range)), - |(name, range)| (name, false, range), - ), + map(pair(keyword_or_function, opt(datatype_range)), |(name, range)| { + (name, false, range) + }), map( pair( delimited(ws(tag("'")), keyword_or_function, ws(tag("'"))), @@ -789,13 +759,12 @@ fn parse_literal(input: &str) -> IResult<&str, SyntaxComponent> { literal: ",".to_string(), multipliers: vec![SyntaxComponentMultiplier::Once], }), - map( - delimited(tag("'"), take_while(|c| c != '\''), tag("'")), - |s: &str| SyntaxComponent::Literal { + map(delimited(tag("'"), take_while(|c| c != '\''), tag("'")), |s: &str| { + SyntaxComponent::Literal { literal: s.to_string(), multipliers: vec![SyntaxComponentMultiplier::Once], - }, - ), + } + }), ))(input) } @@ -832,9 +801,8 @@ fn parse(input: &str) -> IResult<&str, SyntaxComponent> { #[cfg(test)] mod tests { - use crate::property_definitions::get_css_definitions; - use super::*; + use crate::matcher::property_definitions::get_css_definitions; #[test] fn test_compile_empty() { @@ -1016,10 +984,7 @@ mod tests { parts.unwrap(), CssSyntaxTree::new(vec![SyntaxComponent::GenericKeyword { keyword: "color".to_string(), - multipliers: vec![SyntaxComponentMultiplier::CommaSeparatedRepeat( - 1, - u32::MAX as usize - )], + multipliers: vec![SyntaxComponentMultiplier::CommaSeparatedRepeat(1, u32::MAX as usize)], }]) ); @@ -1219,10 +1184,7 @@ mod tests { datatype: "foo".to_string(), quoted: false, range: RangeType::empty(), - multipliers: vec![SyntaxComponentMultiplier::CommaSeparatedRepeat( - 1, - u32::MAX as usize - )], + multipliers: vec![SyntaxComponentMultiplier::CommaSeparatedRepeat(1, u32::MAX as usize)], }] ); } @@ -1403,9 +1365,7 @@ mod tests { } ); - let c = CssSyntax::new("left | right || top && bottom") - .compile() - .unwrap(); + let c = CssSyntax::new("left | right || top && bottom").compile().unwrap(); assert_eq!( c.components[0], SyntaxComponent::Group { @@ -1444,9 +1404,7 @@ mod tests { } ); - let c = CssSyntax::new("left || right | top && bottom") - .compile() - .unwrap(); + let c = CssSyntax::new("left || right | top && bottom").compile().unwrap(); assert_eq!( c.components[0], SyntaxComponent::Group { @@ -1485,9 +1443,7 @@ mod tests { } ); - let c = CssSyntax::new("left && right || top | bottom") - .compile() - .unwrap(); + let c = CssSyntax::new("left && right || top | bottom").compile().unwrap(); assert_eq!( c.components[0], SyntaxComponent::Group { @@ -1526,9 +1482,7 @@ mod tests { } ); - let c = CssSyntax::new("left right || top | bottom") - .compile() - .unwrap(); + let c = CssSyntax::new("left right || top | bottom").compile().unwrap(); assert_eq!( c.components[0], SyntaxComponent::Group { @@ -1567,9 +1521,7 @@ mod tests { } ); - let c = CssSyntax::new("left | right || top | bottom") - .compile() - .unwrap(); + let c = CssSyntax::new("left | right || top | bottom").compile().unwrap(); assert_eq!( c.components[0], SyntaxComponent::Group { @@ -1602,9 +1554,7 @@ mod tests { } ); - let c = CssSyntax::new("left || right | top || bottom") - .compile() - .unwrap(); + let c = CssSyntax::new("left || right | top || bottom").compile().unwrap(); assert_eq!( c.components[0], SyntaxComponent::Group { @@ -1801,49 +1751,31 @@ mod tests { // @todo: These tests should also check if the syntax is correct, not only if it can // compile. The output could still be wrong. assert!(CssSyntax::new("le, ri ,co , bt,tp").compile().is_ok()); - assert!(CssSyntax::new("left | right | center && top") - .compile() - .is_ok()); + assert!(CssSyntax::new("left | right | center && top").compile().is_ok()); assert!(CssSyntax::new("left , right color()").compile().is_ok()); assert!(CssSyntax::new("left , right color() ").compile().is_ok()); assert!(CssSyntax::new("le, ri ,co , bt,tp").compile().is_ok()); assert!(CssSyntax::new("left, right color()").compile().is_ok()); - assert!(CssSyntax::new("left | right | center && top") + assert!(CssSyntax::new("left | right | center && top").compile().is_ok()); + assert!(CssSyntax::new("left | right | center && top ") .compile() .is_ok()); - assert!(CssSyntax::new("left | right | center && top ") + assert!(CssSyntax::new("[ [ ? ]]").compile().is_ok()); + assert!(CssSyntax::new("[ [ center | [ top | bottom ] ]]").compile().is_ok()); + assert!(CssSyntax::new("[ ? ]").compile().is_ok()); + assert!(CssSyntax::new("[ center ? ]").compile().is_ok()); + assert!(CssSyntax::new("center | [ top | bottom ] ") .compile() .is_ok()); - assert!(CssSyntax::new("[ [ ? ]]") + assert!(CssSyntax::new("[ center | [ top | bottom ] ]") .compile() .is_ok()); - assert!(CssSyntax::new("[ [ center | [ top | bottom ] ]]") + assert!(CssSyntax::new("[ center | [ top | bottom ] ? ]") .compile() .is_ok()); - assert!(CssSyntax::new("[ ? ]").compile().is_ok()); - assert!(CssSyntax::new("[ center ? ]") + assert!(CssSyntax::new("[ [ center | [ top | bottom ] ? ]]") .compile() .is_ok()); - assert!( - CssSyntax::new("center | [ top | bottom ] ") - .compile() - .is_ok() - ); - assert!( - CssSyntax::new("[ center | [ top | bottom ] ]") - .compile() - .is_ok() - ); - assert!( - CssSyntax::new("[ center | [ top | bottom ] ? ]") - .compile() - .is_ok() - ); - assert!( - CssSyntax::new("[ [ center | [ top | bottom ] ? ]]") - .compile() - .is_ok() - ); assert!(CssSyntax::new("[ [ top | center | bottom | ]| [ center | [ left | right ] ? ] && [ center | [ top | bottom ] ? ]]").compile().is_ok()); assert!(CssSyntax::new("[ [ left | center | right | top | bottom | ]| [ left | center | right | ] [ top | center | bottom | ]| [ center | [ left | right ] ? ] && [ center | [ top | bottom ] ? ]]").compile().is_ok()); } @@ -1962,9 +1894,7 @@ mod tests { }]) ); - let c = CssSyntax::new("left && right | foo || bar baz") - .compile() - .unwrap(); + let c = CssSyntax::new("left && right | foo || bar baz").compile().unwrap(); assert_eq!( c, CssSyntaxTree::new(vec![SyntaxComponent::Group { @@ -2013,9 +1943,7 @@ mod tests { }]) ); - let c = CssSyntax::new("[ left ] [ right ] [ top ]") - .compile() - .unwrap(); + let c = CssSyntax::new("[ left ] [ right ] [ top ]").compile().unwrap(); assert_eq!( c, CssSyntaxTree::new(vec![SyntaxComponent::Group { @@ -2127,9 +2055,7 @@ mod tests { }]) ); - let c = CssSyntax::new("[ [ left ] [ right ] [ top ] ] a") - .compile() - .unwrap(); + let c = CssSyntax::new("[ [ left ] [ right ] [ top ] ] a").compile().unwrap(); assert_eq!( c, CssSyntaxTree::new(vec![SyntaxComponent::Group { @@ -2162,9 +2088,7 @@ mod tests { }]) ); - let c = CssSyntax::new("[ [ left ] | [ right ] [ top ] ]") - .compile() - .unwrap(); + let c = CssSyntax::new("[ [ left ] | [ right ] [ top ] ]").compile().unwrap(); assert_eq!( c, CssSyntaxTree::new(vec![SyntaxComponent::Group { @@ -2312,15 +2236,11 @@ mod tests { components: vec![ SyntaxComponent::GenericKeyword { keyword: "left".to_string(), - multipliers: vec![ - SyntaxComponentMultiplier::Once - ], + multipliers: vec![SyntaxComponentMultiplier::Once], }, SyntaxComponent::GenericKeyword { keyword: "right".to_string(), - multipliers: vec![ - SyntaxComponentMultiplier::Once - ], + multipliers: vec![SyntaxComponentMultiplier::Once], }, ], multipliers: vec![SyntaxComponentMultiplier::Once], @@ -2332,9 +2252,7 @@ mod tests { min: NumberOrInfinity::None, max: NumberOrInfinity::None, }, - multipliers: vec![ - SyntaxComponentMultiplier::Optional - ], + multipliers: vec![SyntaxComponentMultiplier::Optional], }, ], multipliers: vec![SyntaxComponentMultiplier::Once], @@ -2357,15 +2275,11 @@ mod tests { components: vec![ SyntaxComponent::GenericKeyword { keyword: "top".to_string(), - multipliers: vec![ - SyntaxComponentMultiplier::Once - ], + multipliers: vec![SyntaxComponentMultiplier::Once], }, SyntaxComponent::GenericKeyword { keyword: "bottom".to_string(), - multipliers: vec![ - SyntaxComponentMultiplier::Once - ], + multipliers: vec![SyntaxComponentMultiplier::Once], }, ], multipliers: vec![SyntaxComponentMultiplier::Once], @@ -2377,9 +2291,7 @@ mod tests { min: NumberOrInfinity::None, max: NumberOrInfinity::None, }, - multipliers: vec![ - SyntaxComponentMultiplier::Optional - ], + multipliers: vec![SyntaxComponentMultiplier::Optional], }, ], multipliers: vec![SyntaxComponentMultiplier::Once], @@ -2395,11 +2307,9 @@ mod tests { }]) ); - let c = CssSyntax::new( - "[ [ left+ ] | [ center? ] [ top# ]{1,3} | [ center1 ]? && [ center2 ] ]*", - ) - .compile() - .unwrap(); + let c = CssSyntax::new("[ [ left+ ] | [ center? ] [ top# ]{1,3} | [ center1 ]? && [ center2 ] ]*") + .compile() + .unwrap(); assert_eq!( c, CssSyntaxTree::new(vec![SyntaxComponent::Group { diff --git a/crates/gosub_styling/src/syntax_matcher.rs b/crates/gosub_css3/src/matcher/syntax_matcher.rs similarity index 90% rename from crates/gosub_styling/src/syntax_matcher.rs rename to crates/gosub_css3/src/matcher/syntax_matcher.rs index 822a2775b..8e70cb4e0 100644 --- a/crates/gosub_styling/src/syntax_matcher.rs +++ b/crates/gosub_css3/src/matcher/syntax_matcher.rs @@ -1,8 +1,7 @@ -use gosub_css3::colors::{is_named_color, is_system_color}; -use gosub_css3::stylesheet::CssValue; - -use crate::shorthands::{copy_resolver, ShorthandResolver}; -use crate::syntax::{GroupCombinators, SyntaxComponent, SyntaxComponentMultiplier}; +use crate::colors::{is_named_color, is_system_color}; +use crate::matcher::shorthands::{copy_resolver, ShorthandResolver}; +use crate::matcher::syntax::{GroupCombinators, SyntaxComponent, SyntaxComponentMultiplier}; +use crate::stylesheet::CssValue; /// Structure to return from a matching function. #[derive(Debug, Clone)] @@ -17,9 +16,8 @@ pub struct MatchResult<'a> { #[allow(dead_code)] const LENGTH_UNITS: [&str; 31] = [ - "cap", "ch", "em", "ex", "ic", "lh", "rcap", "rch", "rem", "rex", "ric", "rlh", "vh", "vw", - "vmax", "vmin", "vb", "vi", "cqw", "cqh", "cqi", "cqb", "cqmin", "cqmax", "px", "cm", "mm", - "Q", "in", "pc", "pt", + "cap", "ch", "em", "ex", "ic", "lh", "rcap", "rch", "rem", "rex", "ric", "rlh", "vh", "vw", "vmax", "vmin", "vb", + "vi", "cqw", "cqh", "cqi", "cqb", "cqmin", "cqmax", "px", "cm", "mm", "Q", "in", "pc", "pt", ]; /// A CSS Syntax Tree is a tree sof CSS syntax components that can be used to match against CSS values. @@ -178,8 +176,7 @@ fn match_component<'a>( // CSV loop loop { - let inner_result = - match_component_inner(input, component, copy_resolver(&mut shorthand_resolver)); + let inner_result = match_component_inner(input, component, copy_resolver(&mut shorthand_resolver)); if !comma_separated { // We don't need to check for comma separated values, so just return this result return inner_result; @@ -234,25 +231,17 @@ fn match_component_group<'a>( ) -> MatchResult<'a> { match &component { SyntaxComponent::Group { - components, - combinator, - .. + components, combinator, .. } => { // println!("We need to do a group match on {:?}, our value is: {:?}", combinator, input); match combinator { - GroupCombinators::Juxtaposition => { - match_group_juxtaposition(input, components, shorthand_resolver) - } - GroupCombinators::AllAnyOrder => { - match_group_all_any_order(input, components, shorthand_resolver) - } + GroupCombinators::Juxtaposition => match_group_juxtaposition(input, components, shorthand_resolver), + GroupCombinators::AllAnyOrder => match_group_all_any_order(input, components, shorthand_resolver), GroupCombinators::AtLeastOneAnyOrder => { match_group_at_least_one_any_order(input, components, shorthand_resolver) } - GroupCombinators::ExactlyOne => { - match_group_exactly_one(input, components, shorthand_resolver) - } + GroupCombinators::ExactlyOne => match_group_exactly_one(input, components, shorthand_resolver), } } e => { @@ -262,10 +251,7 @@ fn match_component_group<'a>( } /// Matches a single component value -fn match_component_single<'a>( - input: &'a [CssValue], - component: &SyntaxComponent, -) -> MatchResult<'a> { +fn match_component_single<'a>(input: &'a [CssValue], component: &SyntaxComponent) -> MatchResult<'a> { // Get the first value from the input which we will use for matching let value = input.first().unwrap(); @@ -295,20 +281,14 @@ fn match_component_single<'a>( "angle" => match value { CssValue::Zero => return first_match(input), CssValue::Unit(_, u) if u.eq_ignore_ascii_case("deg") => return first_match(input), - CssValue::Unit(_, u) if u.eq_ignore_ascii_case("grad") => { - return first_match(input) - } + CssValue::Unit(_, u) if u.eq_ignore_ascii_case("grad") => return first_match(input), CssValue::Unit(_, u) if u.eq_ignore_ascii_case("rad") => return first_match(input), - CssValue::Unit(_, u) if u.eq_ignore_ascii_case("turn") => { - return first_match(input) - } + CssValue::Unit(_, u) if u.eq_ignore_ascii_case("turn") => return first_match(input), _ => {} }, "length" => match value { CssValue::Zero => return first_match(input), - CssValue::Unit(_, u) if LENGTH_UNITS.contains(&u.as_str()) => { - return first_match(input) - } + CssValue::Unit(_, u) if LENGTH_UNITS.contains(&u.as_str()) => return first_match(input), _ => {} }, "system-color" => { @@ -363,10 +343,7 @@ fn match_component_single<'a>( match value { CssValue::Number(n) if *n == 0.0 => return first_match(input), CssValue::Unit(n, u) => { - if unit.contains(u) - && *n >= from.unwrap_or(f32min) - && *n <= to.unwrap_or(f32max) - { + if unit.contains(u) && *n >= from.unwrap_or(f32min) && *n <= to.unwrap_or(f32max) { return first_match(input); } } @@ -398,9 +375,7 @@ fn match_component_single<'a>( // let list = CssValue::List(c_args.clone()); // return match_internal(&list, arguments); } - SyntaxComponent::Value { - value: css_value, .. - } => { + SyntaxComponent::Value { value: css_value, .. } => { if value == css_value { return first_match(input); } @@ -762,8 +737,7 @@ fn multiplier_fulfilled(component: &SyntaxComponent, cnt: usize) -> Fulfillment .filter(|m| { !matches!( m, - SyntaxComponentMultiplier::AtLeastOneValue - | SyntaxComponentMultiplier::CommaSeparatedRepeat(_, _) + SyntaxComponentMultiplier::AtLeastOneValue | SyntaxComponentMultiplier::CommaSeparatedRepeat(_, _) ) }) .collect(); @@ -818,12 +792,10 @@ fn first_match(input: &[CssValue]) -> MatchResult { #[cfg(test)] mod tests { - use gosub_css3::stylesheet::CssValue; - - use crate::property_definitions::{get_css_definitions, PropertyDefinition}; - use crate::syntax::CssSyntax; use super::*; + use crate::matcher::property_definitions::{get_css_definitions, PropertyDefinition}; + use crate::matcher::syntax::CssSyntax; macro_rules! str { ($s:expr) => { @@ -925,12 +897,7 @@ mod tests { assert_true!(tree.matches(&[CssValue::None, str!("auto"), str!("block")])); assert_false!(tree.matches(&[str!("auto"), str!("block")])); assert_false!(tree.matches(&[CssValue::None, str!("block")])); - assert_false!(tree.matches(&[ - str!("block"), - str!("block"), - CssValue::None, - CssValue::None - ])); + assert_false!(tree.matches(&[str!("block"), str!("block"), CssValue::None, CssValue::None])); } #[test] @@ -956,12 +923,7 @@ mod tests { CssValue::Comma, str!("block"), ])); - assert_false!(tree.matches(&[ - str!("block"), - str!("block"), - CssValue::None, - CssValue::None, - ])); + assert_false!(tree.matches(&[str!("block"), str!("block"), CssValue::None, CssValue::None,])); } #[test] @@ -984,13 +946,7 @@ mod tests { let res = match_group_juxtaposition(&input, components, None); assert_not_match!(res); - let input = [ - str!("none"), - str!("block"), - str!("block"), - str!("auto"), - str!("none"), - ]; + let input = [str!("none"), str!("block"), str!("block"), str!("auto"), str!("none")]; let res = match_group_juxtaposition(&input, components, None); assert_not_match!(res); @@ -1044,13 +1000,7 @@ mod tests { let res = match_group_all_any_order(&input, components, None); assert_match!(res); - let input = [ - str!("none"), - str!("block"), - str!("block"), - str!("auto"), - str!("none"), - ]; + let input = [str!("none"), str!("block"), str!("block"), str!("auto"), str!("none")]; let res = match_group_all_any_order(&input, components, None); assert_not_match!(res); @@ -1115,26 +1065,20 @@ mod tests { let tree = CssSyntax::new("foo bar baz").compile().unwrap(); assert_false!(tree.clone().matches(&[str!("foo")])); assert_false!(tree.clone().matches(&[str!("foo")])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("baz"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("baz"),])); assert_false!(tree.clone().matches(&[str!("foo"), str!("baz"),])); let tree = CssSyntax::new("foo bar?").compile().unwrap(); dbg!(&tree); assert_true!(tree.clone().matches(&[str!("foo")])); assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"),])); - assert_false!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("bar"),])); + assert_false!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("bar"),])); assert_false!(tree.clone().matches(&[str!("bar"), str!("foo"),])); let tree = CssSyntax::new("foo bar? baz").compile().unwrap(); assert_false!(tree.clone().matches(&[str!("foo")])); assert_true!(tree.clone().matches(&[str!("foo"), str!("baz"),])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("baz"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("baz"),])); assert_false!(tree .clone() @@ -1150,9 +1094,7 @@ mod tests { let tree = CssSyntax::new("foo bar* baz").compile().unwrap(); assert_false!(tree.clone().matches(&[str!("foo")])); assert_false!(tree.clone().matches(&[str!("foo")])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("baz"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("baz"),])); assert_true!(tree.clone().matches(&[str!("foo"), str!("baz"),])); assert_true!(tree.clone().matches(&[ str!("foo"), @@ -1175,9 +1117,7 @@ mod tests { assert_true!(tree.clone().matches(&[str!("foo")])); assert_true!(tree.clone().matches(&[str!("foo")])); assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"),])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("bar"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("bar"),])); assert_false!(tree.clone().matches(&[str!("bar"), str!("foo"),])); } @@ -1186,9 +1126,7 @@ mod tests { let tree = CssSyntax::new("foo bar+ baz").compile().unwrap(); assert_false!(tree.clone().matches(&[str!("foo")])); assert_false!(tree.clone().matches(&[str!("foo")])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("baz"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("baz"),])); assert_false!(tree.clone().matches(&[str!("foo"), str!("baz"),])); assert_true!(tree.clone().matches(&[ str!("foo"), @@ -1211,18 +1149,14 @@ mod tests { assert_false!(tree.clone().matches(&[str!("foo")])); assert_false!(tree.clone().matches(&[str!("bar")])); assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"),])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("bar"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("bar"),])); assert_false!(tree.clone().matches(&[str!("bar"), str!("foo"),])); let tree = CssSyntax::new("foo+ bar+").compile().unwrap(); assert_false!(tree.clone().matches(&[str!("foo")])); assert_false!(tree.clone().matches(&[str!("bar")])); assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"),])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("bar"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("bar"),])); assert_true!(tree .clone() .matches(&[str!("foo"), str!("foo"), str!("bar"), str!("bar"),])); @@ -1235,20 +1169,14 @@ mod tests { let tree = CssSyntax::new("foo bar{1,3} baz").compile().unwrap(); assert_false!(tree.clone().matches(&[str!("foo")])); assert_false!(tree.clone().matches(&[str!("foo")])); - assert_true!(tree - .clone() - .matches(&[str!("foo"), str!("bar"), str!("baz"),])); + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("baz"),])); assert_false!(tree.clone().matches(&[str!("foo"), str!("baz"),])); assert_true!(tree .clone() .matches(&[str!("foo"), str!("bar"), str!("bar"), str!("baz"),])); - assert_true!(tree.clone().matches(&[ - str!("foo"), - str!("bar"), - str!("bar"), - str!("bar"), - str!("baz"), - ])); + assert_true!(tree + .clone() + .matches(&[str!("foo"), str!("bar"), str!("bar"), str!("bar"), str!("baz"),])); assert_false!(tree.clone().matches(&[ str!("foo"), str!("bar"), @@ -1270,16 +1198,10 @@ mod tests { assert_true!(tree.clone().matches(&[str!("foo")])); assert_true!(tree.clone().matches(&[str!("foo")])); assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"),])); - assert_true!(tree + assert_true!(tree.clone().matches(&[str!("foo"), str!("bar"), str!("bar"),])); + assert_false!(tree .clone() - .matches(&[str!("foo"), str!("bar"), str!("bar"),])); - assert_false!(tree.clone().matches(&[ - str!("foo"), - str!("bar"), - str!("bar"), - str!("bar"), - str!("bar"), - ])); + .matches(&[str!("foo"), str!("bar"), str!("bar"), str!("bar"), str!("bar"),])); assert_false!(tree.clone().matches(&[str!("bar"), str!("foo"),])); } @@ -1291,11 +1213,9 @@ mod tests { PropertyDefinition { name: "testprop".to_string(), computed: vec![], - syntax: CssSyntax::new( - "[ left | right ] ? | [ top | bottom ] | [ top | bottom ]", - ) - .compile() - .unwrap(), + syntax: CssSyntax::new("[ left | right ] ? | [ top | bottom ] | [ top | bottom ]") + .compile() + .unwrap(), inherited: false, initial_value: None, resolved: false, @@ -1306,12 +1226,8 @@ mod tests { let prop = definitions.find_property("testprop").unwrap(); - assert_true!(prop - .clone() - .matches(&[str!("left"), CssValue::Unit(5.0, "px".into()),])); - assert_true!(prop - .clone() - .matches(&[str!("top"), CssValue::Unit(5.0, "px".into()),])); + assert_true!(prop.clone().matches(&[str!("left"), CssValue::Unit(5.0, "px".into()),])); + assert_true!(prop.clone().matches(&[str!("top"), CssValue::Unit(5.0, "px".into()),])); assert_true!(prop .clone() .matches(&[str!("bottom"), CssValue::Unit(5.0, "px".into()),])); @@ -1362,21 +1278,16 @@ mod tests { assert_true!(prop .clone() .matches(&[CssValue::Percentage(10.0), CssValue::Percentage(20.0),])); - assert_true!(prop.clone().matches(&[ - CssValue::Unit(10.0, "px".into()), - CssValue::Percentage(20.0), - ])); assert_true!(prop .clone() - .matches(&[str!("left"), CssValue::Percentage(20.0),])); + .matches(&[CssValue::Unit(10.0, "px".into()), CssValue::Percentage(20.0),])); + assert_true!(prop.clone().matches(&[str!("left"), CssValue::Percentage(20.0),])); assert_true!(prop .clone() .matches(&[CssValue::Unit(10.0, "px".into()), str!("center"),])); - assert_true!(prop - .clone() - .matches(&[CssValue::Percentage(10.0), str!("top"),])); + assert_true!(prop.clone().matches(&[CssValue::Percentage(10.0), str!("top"),])); assert_true!(prop.clone().matches(&[str!("right")])); @@ -1391,9 +1302,7 @@ mod tests { PropertyDefinition { name: "testprop".to_string(), computed: vec![], - syntax: CssSyntax::new("foo | [ foo [ foo | bar ] ]") - .compile() - .unwrap(), + syntax: CssSyntax::new("foo | [ foo [ foo | bar ] ]").compile().unwrap(), inherited: false, initial_value: None, resolved: false, @@ -1575,9 +1484,7 @@ mod tests { assert_true!(prop.clone().matches(&[str!("left"),])); assert_true!(prop.clone().matches(&[str!("right"),])); - assert_true!(prop - .clone() - .matches(&[str!("top"), CssValue::Unit(10.0, "px".into()),])); + assert_true!(prop.clone().matches(&[str!("top"), CssValue::Unit(10.0, "px".into()),])); assert_true!(prop .clone() .matches(&[str!("bottom"), CssValue::Unit(10.0, "px".into()),])); @@ -1591,22 +1498,10 @@ mod tests { let tree = CssSyntax::new("[foo | bar | baz]#").compile().unwrap(); assert_true!(tree.matches(&[str!("foo")])); assert_true!(tree.matches(&[str!("foo"), CssValue::Comma, str!("foo")])); - assert_true!(tree.matches(&[ - str!("foo"), - CssValue::Comma, - str!("foo"), - CssValue::Comma, - str!("foo") - ])); + assert_true!(tree.matches(&[str!("foo"), CssValue::Comma, str!("foo"), CssValue::Comma, str!("foo")])); assert_true!(tree.matches(&[str!("foo"), CssValue::Comma, str!("bar")])); assert_true!(tree.matches(&[str!("foo"), CssValue::Comma, str!("baz")])); - assert_true!(tree.matches(&[ - str!("foo"), - CssValue::Comma, - str!("bar"), - CssValue::Comma, - str!("baz") - ])); + assert_true!(tree.matches(&[str!("foo"), CssValue::Comma, str!("bar"), CssValue::Comma, str!("baz")])); assert_false!(tree.matches(&[str!("foo"), CssValue::Comma])); assert_false!(tree.matches(&[str!("foo"), CssValue::Comma, str!("bar"), CssValue::Comma])); diff --git a/crates/gosub_css3/src/matcher/walker.rs b/crates/gosub_css3/src/matcher/walker.rs new file mode 100644 index 000000000..dccfc5440 --- /dev/null +++ b/crates/gosub_css3/src/matcher/walker.rs @@ -0,0 +1,48 @@ +use crate::matcher::syntax_matcher::CssSyntaxTree; + +pub struct MatchWalker { + tree: CssSyntaxTree, +} + +impl MatchWalker { + pub fn new(tree: &CssSyntaxTree) -> Self { + Self { tree: tree.clone() } + } + + // pub fn walk(&self) { + // let _ = self.inner_walk(&self.tree); + // } + // + // fn inner_walk(&self, node: &Node) -> Result<(), std::io::Error> { + // match node.node_type.deref() { + // NodeType::StyleSheet { children } => { + // for child in children.iter() { + // self.inner_walk(child)?; + // } + // } + // NodeType::Rule { block, .. } => { + // if block.is_some() { + // self.inner_walk(block.as_ref().unwrap())?; + // } + // } + // NodeType::AtRule { block, .. } => { + // if block.is_some() { + // self.inner_walk(block.as_ref().unwrap())?; + // } + // } + // NodeType::Block { children, .. } => { + // for child in children.iter() { + // self.inner_walk(child)?; + // } + // } + // NodeType::Declaration { property, value, .. } => { + // println!("Matching property '{}' against value '{:?}'", property, value); + // + // + // } + // _ => {} + // } + // + // Ok(()) + // } +} diff --git a/crates/gosub_css3/src/node.rs b/crates/gosub_css3/src/node.rs index 34e09fbee..d1a585ae8 100644 --- a/crates/gosub_css3/src/node.rs +++ b/crates/gosub_css3/src/node.rs @@ -395,11 +395,7 @@ impl Display for Node { .map(|s| s.to_string()) .collect::>() .join(", "), - NodeType::Selector { children } => children - .iter() - .map(|s| s.to_string()) - .collect::>() - .join(""), + NodeType::Selector { children } => children.iter().map(|s| s.to_string()).collect::>().join(""), NodeType::IdSelector { value } => value.clone(), NodeType::Ident { value } => value.clone(), NodeType::Number { value } => value.to_string(), @@ -422,10 +418,7 @@ impl Display for Node { value, flags, } => { - let matcher = matcher - .as_ref() - .map(|m| m.to_string()) - .unwrap_or("".to_string()); + let matcher = matcher.as_ref().map(|m| m.to_string()).unwrap_or("".to_string()); format!("[{}{}{}{}]", name, matcher, value, flags) } NodeType::PseudoClassSelector { value } => format!(":{}", value), @@ -441,10 +434,7 @@ impl Display for Node { } NodeType::Combinator { value } => value.clone(), NodeType::Nth { nth, selector } => { - let sel = selector - .as_ref() - .map(|s| s.to_string()) - .unwrap_or("".to_string()); + let sel = selector.as_ref().map(|s| s.to_string()).unwrap_or("".to_string()); format!("{}{}", nth, sel) } NodeType::AnPlusB { a, b } => format!("{}n+{}", a, b), diff --git a/crates/gosub_css3/src/parser.rs b/crates/gosub_css3/src/parser.rs index 6d2649f18..586486b1f 100644 --- a/crates/gosub_css3/src/parser.rs +++ b/crates/gosub_css3/src/parser.rs @@ -1,5 +1,6 @@ use crate::tokenizer::{Number, Token, TokenType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::{CssError, CssResult}; mod anplusb; mod at_rule; @@ -21,11 +22,11 @@ mod value; impl Css3<'_> { /// Consumes a specific token - pub fn consume(&mut self, token_type: TokenType) -> Result { + pub fn consume(&mut self, token_type: TokenType) -> CssResult { let t = self.tokenizer.consume(); if t.token_type != token_type { - return Err(Error::new( - format!("Expected {:?}, got {:?}", token_type, t), + return Err(CssError::with_location( + format!("Expected {:?}, got {:?}", token_type, t).as_str(), self.tokenizer.current_location(), )); } @@ -34,60 +35,60 @@ impl Css3<'_> { } /// Consumes any token - pub fn consume_any(&mut self) -> Result { + pub fn consume_any(&mut self) -> CssResult { Ok(self.tokenizer.consume()) } - pub fn consume_function(&mut self) -> Result { + pub fn consume_function(&mut self) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { TokenType::Function(name) => Ok(name), - _ => Err(Error::new( - format!("Expected function, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected function, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn consume_any_number(&mut self) -> Result { + pub fn consume_any_number(&mut self) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { TokenType::Number(value) => Ok(value), - _ => Err(Error::new( - format!("Expected number, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected number, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn consume_any_delim(&mut self) -> Result { + pub fn consume_any_delim(&mut self) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { TokenType::Delim(c) => Ok(c), - _ => Err(Error::new( - format!("Expected delimiter, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected delimiter, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn consume_any_string(&mut self) -> Result { + pub fn consume_any_string(&mut self) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { TokenType::QuotedString(s) => Ok(s), - _ => Err(Error::new( - format!("Expected string, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected string, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn consume_delim(&mut self, delimiter: char) -> Result { + pub fn consume_delim(&mut self, delimiter: char) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { TokenType::Delim(c) if c == delimiter => Ok(c), - _ => Err(Error::new( - format!("Expected delimiter '{}', got {:?}", delimiter, t), + _ => Err(CssError::with_location( + format!("Expected delimiter '{}', got {:?}", delimiter, t).as_str(), self.tokenizer.current_location(), )), } @@ -108,29 +109,29 @@ impl Css3<'_> { } } - pub fn consume_ident_ci(&mut self, ident: &str) -> Result { + pub fn consume_ident_ci(&mut self, ident: &str) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { TokenType::Ident(s) if s.eq_ignore_ascii_case(ident) => Ok(s), - _ => Err(Error::new( - format!("Expected ident, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected ident, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn consume_ident(&mut self, ident: &str) -> Result { + pub fn consume_ident(&mut self, ident: &str) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { TokenType::Ident(s) if s == ident => Ok(s), - _ => Err(Error::new( - format!("Expected ident, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected ident, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn consume_any_ident(&mut self) -> Result { + pub fn consume_any_ident(&mut self) -> CssResult { let t = self.tokenizer.consume(); match t.token_type { @@ -138,21 +139,21 @@ impl Css3<'_> { let t = self.tokenizer.consume(); match t.token_type { TokenType::Ident(s) => Ok(format!(".{}", s)), - _ => Err(Error::new( - format!("Expected ident, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected ident, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } TokenType::Ident(s) => Ok(s), - _ => Err(Error::new( - format!("Expected ident, got {:?}", t), + _ => Err(CssError::with_location( + format!("Expected ident, got {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn consume_raw_condition(&mut self) -> Result { + pub fn consume_raw_condition(&mut self) -> CssResult { let start = self.tokenizer.tell(); while !self.tokenizer.eof() { diff --git a/crates/gosub_css3/src/parser/anplusb.rs b/crates/gosub_css3/src/parser/anplusb.rs index 5782acc3e..de440997e 100644 --- a/crates/gosub_css3/src/parser/anplusb.rs +++ b/crates/gosub_css3/src/parser/anplusb.rs @@ -1,20 +1,17 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::{Number, TokenType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::{CssError, CssResult}; impl Css3<'_> { - fn do_dimension_block( - &mut self, - value: Number, - unit: String, - ) -> Result<(String, String), Error> { + fn do_dimension_block(&mut self, value: Number, unit: String) -> CssResult<(String, String)> { log::trace!("do_dimension_block"); let value = value.to_string(); if unit.chars().nth(0).unwrap().to_lowercase().to_string() != "n" { - return Err(Error::new( - format!("Expected n, found {}", unit).to_string(), + return Err(CssError::with_location( + format!("Expected n, found {}", unit).as_str(), self.tokenizer.current_location(), )); } @@ -25,24 +22,14 @@ impl Css3<'_> { }) } - fn check_integer( - &mut self, - value: &str, - offset: usize, - allow_sign: bool, - ) -> Result { - let sign = value - .chars() - .nth(offset) - .unwrap_or(' ') - .to_lowercase() - .to_string(); + fn check_integer(&mut self, value: &str, offset: usize, allow_sign: bool) -> CssResult { + let sign = value.chars().nth(offset).unwrap_or(' ').to_lowercase().to_string(); let mut pos = offset; if sign == "+" || sign == "-" { if !allow_sign { - return Err(Error::new( - format!("Unexpected sign {}", sign).to_string(), + return Err(CssError::with_location( + format!("Unexpected sign {}", sign).as_str(), self.tokenizer.current_location(), )); } @@ -58,16 +45,11 @@ impl Css3<'_> { Ok(true) } - fn expect_char(&mut self, value: &str, c: &str, offset: usize) -> Result { - let nval = value - .chars() - .nth(offset) - .unwrap_or(' ') - .to_lowercase() - .to_string(); + fn expect_char(&mut self, value: &str, c: &str, offset: usize) -> CssResult { + let nval = value.chars().nth(offset).unwrap_or(' ').to_lowercase().to_string(); if nval != c { - return Err(Error::new( - format!("Expected {}", c).to_string(), + return Err(CssError::with_location( + format!("Expected {}", c).as_str(), self.tokenizer.current_location(), )); } @@ -75,7 +57,7 @@ impl Css3<'_> { Ok(true) } - fn parse_anplusb_b(&mut self) -> Result { + fn parse_anplusb_b(&mut self) -> CssResult { log::trace!("parse_anplusb_b"); self.consume_whitespace_comments(); @@ -107,12 +89,12 @@ impl Css3<'_> { false } _ => { - return Err(Error::new( + return Err(CssError::with_location( format!( "Expected +, - or number, found {:?}", self.tokenizer.lookahead(0).token_type ) - .to_string(), + .as_str(), self.tokenizer.current_location(), )); } @@ -128,7 +110,7 @@ impl Css3<'_> { Ok(val.to_string()) } - fn do_negative_block(&mut self, value: &str) -> Result<(String, String), Error> { + fn do_negative_block(&mut self, value: &str) -> CssResult<(String, String)> { log::trace!("do_negative_block"); let a = String::from("-1"); @@ -162,7 +144,7 @@ impl Css3<'_> { Ok((a, b)) } - fn do_plus_block(&mut self, value: &str) -> Result<(String, String), Error> { + fn do_plus_block(&mut self, value: &str) -> CssResult<(String, String)> { log::trace!("do_plus_block"); let a = String::from("1"); @@ -196,7 +178,7 @@ impl Css3<'_> { Ok((a, b)) } - pub fn parse_anplusb(&mut self) -> Result { + pub fn parse_anplusb(&mut self) -> CssResult { log::trace!("parse_anplusb"); let loc = self.tokenizer.current_location(); @@ -228,8 +210,8 @@ impl Css3<'_> { } _ => { self.tokenizer.reconsume(); - return Err(Error::new( - "Expected anplusb".to_string(), + return Err(CssError::with_location( + "Expected anplusb", self.tokenizer.current_location(), )); } @@ -251,6 +233,8 @@ impl Css3<'_> { mod test { use super::*; use gosub_shared::byte_stream::{ByteStream, Encoding}; + use gosub_shared::traits::css3::CssOrigin; + use gosub_shared::traits::ParserConfig; macro_rules! test { ($func:ident, $input:expr, $expected:expr) => { @@ -258,7 +242,7 @@ mod test { stream.read_from_str($input, Some(Encoding::UTF8)); stream.close(); - let mut parser = crate::Css3::new(&mut stream); + let mut parser = crate::Css3::new(&mut stream, ParserConfig::default(), CssOrigin::User, ""); let result = parser.$func().unwrap(); assert_eq!(result.node_type, $expected); diff --git a/crates/gosub_css3/src/parser/at_rule.rs b/crates/gosub_css3/src/parser/at_rule.rs index 6fb154577..409ad909d 100644 --- a/crates/gosub_css3/src/parser/at_rule.rs +++ b/crates/gosub_css3/src/parser/at_rule.rs @@ -12,7 +12,8 @@ mod supports; use crate::node::{Node, NodeType}; use crate::parser::block::BlockParseMode; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::{CssError, CssResult}; impl Css3<'_> { fn declaration_block_at_rule(&mut self) -> BlockParseMode { @@ -41,10 +42,10 @@ impl Css3<'_> { } } - fn read_sequence_at_rule_prelude(&mut self) -> Result { + fn read_sequence_at_rule_prelude(&mut self) -> CssResult { log::trace!("read_sequence_at_rule_prelude"); - let loc = self.tokenizer.lookahead(0).location.clone(); + let loc = self.tokenizer.lookahead(0).location; Ok(Node::new( NodeType::Container { @@ -54,7 +55,7 @@ impl Css3<'_> { )) } - fn parse_at_rule_prelude(&mut self, name: String) -> Result, Error> { + fn parse_at_rule_prelude(&mut self, name: String) -> CssResult> { log::trace!("parse_at_rule_prelude"); self.consume_whitespace_comments(); @@ -75,24 +76,17 @@ impl Css3<'_> { self.consume_whitespace_comments(); let t = self.tokenizer.lookahead(0); - if !self.tokenizer.eof() - && t.token_type != TokenType::Semicolon - && t.token_type != TokenType::LCurly - { - return Err(Error::new( - "Expected semicolon or left curly brace".to_string(), - t.location.clone(), + if !self.tokenizer.eof() && t.token_type != TokenType::Semicolon && t.token_type != TokenType::LCurly { + return Err(CssError::with_location( + "Expected semicolon or left curly brace", + t.location, )); } Ok(node) } - fn parse_at_rule_block( - &mut self, - name: String, - is_declaration: bool, - ) -> Result, Error> { + fn parse_at_rule_block(&mut self, name: String, is_declaration: bool) -> CssResult> { log::trace!("parse_at_rule_block"); let t = self.tokenizer.consume(); @@ -136,7 +130,7 @@ impl Css3<'_> { // Either the at_rule parsing succeeds as a whole, or not. When not a valid at_rule is found, we // return None if the config.ignore_errors is set to true, otherwise this will return an Err // and is handled by the caller - pub fn parse_at_rule(&mut self, is_declaration: bool) -> Result, Error> { + pub fn parse_at_rule(&mut self, is_declaration: bool) -> CssResult> { log::trace!("parse_at_rule"); let result = self.parse_at_rule_internal(is_declaration); @@ -153,14 +147,14 @@ impl Css3<'_> { Ok(None) } - fn parse_at_rule_internal(&mut self, is_declaration: bool) -> Result { + fn parse_at_rule_internal(&mut self, is_declaration: bool) -> CssResult { let name; let t = self.consume_any()?; if let TokenType::AtKeyword(keyword) = t.token_type { name = keyword; } else { - return Err(Error::new("Expected at keyword".to_string(), t.location)); + return Err(CssError::with_location("Expected at keyword", t.location)); } self.consume_whitespace_comments(); @@ -176,7 +170,7 @@ impl Css3<'_> { prelude, block, }, - t.location.clone(), + t.location, )) } } diff --git a/crates/gosub_css3/src/parser/at_rule/container.rs b/crates/gosub_css3/src/parser/at_rule/container.rs index d1f3129c2..df6025479 100644 --- a/crates/gosub_css3/src/parser/at_rule/container.rs +++ b/crates/gosub_css3/src/parser/at_rule/container.rs @@ -1,9 +1,10 @@ use crate::node::{FeatureKind, Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_at_rule_container_prelude(&mut self) -> Result { + pub fn parse_at_rule_container_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_container_prelude"); let mut children = Vec::new(); @@ -11,15 +12,12 @@ impl Css3<'_> { let t = self.consume_any()?; if let TokenType::Ident(value) = t.token_type { if !["none", "and", "not", "or"].contains(&value.as_str()) { - children.push(Node::new(NodeType::Ident { value }, t.location.clone())); + children.push(Node::new(NodeType::Ident { value }, t.location)); } } children.push(self.parse_condition(FeatureKind::Container)?); - Ok(Node::new( - NodeType::Container { children }, - t.location.clone(), - )) + Ok(Node::new(NodeType::Container { children }, t.location)) } } diff --git a/crates/gosub_css3/src/parser/at_rule/font_face.rs b/crates/gosub_css3/src/parser/at_rule/font_face.rs index 7310a08f5..dc9248e4d 100644 --- a/crates/gosub_css3/src/parser/at_rule/font_face.rs +++ b/crates/gosub_css3/src/parser/at_rule/font_face.rs @@ -1,8 +1,9 @@ use crate::node::Node; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_at_rule_font_face_block(&mut self) -> Result { + pub fn parse_at_rule_font_face_block(&mut self) -> CssResult { log::trace!("parse_at_rule_font_face_block"); todo!(); diff --git a/crates/gosub_css3/src/parser/at_rule/import.rs b/crates/gosub_css3/src/parser/at_rule/import.rs index 9aa50aaa0..088c3f2bc 100644 --- a/crates/gosub_css3/src/parser/at_rule/import.rs +++ b/crates/gosub_css3/src/parser/at_rule/import.rs @@ -1,9 +1,10 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::{CssError, CssResult}; impl Css3<'_> { - pub fn parse_at_rule_import_prelude(&mut self) -> Result { + pub fn parse_at_rule_import_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_import"); let mut children = Vec::new(); @@ -13,19 +14,19 @@ impl Css3<'_> { let t = self.consume_any()?; match t.token_type { TokenType::QuotedString(value) => { - children.push(Node::new(NodeType::String { value }, loc.clone())); + children.push(Node::new(NodeType::String { value }, loc)); } TokenType::Url(url) => { - children.push(Node::new(NodeType::Url { url }, loc.clone())); + children.push(Node::new(NodeType::Url { url }, loc)); } TokenType::Function(name) if name.eq_ignore_ascii_case("url") => { self.tokenizer.reconsume(); children.push(self.parse_url()?); } _ => { - return Err(Error::new( - "Expected string or url()".to_string(), - t.location.clone(), + return Err(CssError::with_location( + "Expected string or url()", + self.tokenizer.current_location(), )); } } @@ -35,7 +36,7 @@ impl Css3<'_> { let t = self.tokenizer.lookahead_sc(0); match t.token_type { TokenType::Ident(value) if value.eq_ignore_ascii_case("layer") => { - children.push(Node::new(NodeType::Ident { value }, t.location.clone())); + children.push(Node::new(NodeType::Ident { value }, t.location)); } TokenType::Function(name) if name.eq_ignore_ascii_case("layer") => { children.push(self.parse_function()?); @@ -69,6 +70,6 @@ impl Css3<'_> { // _ => {} // } - Ok(Node::new(NodeType::ImportList { children }, loc.clone())) + Ok(Node::new(NodeType::ImportList { children }, loc)) } } diff --git a/crates/gosub_css3/src/parser/at_rule/layer.rs b/crates/gosub_css3/src/parser/at_rule/layer.rs index fd797ea1f..6ca1e1dd4 100644 --- a/crates/gosub_css3/src/parser/at_rule/layer.rs +++ b/crates/gosub_css3/src/parser/at_rule/layer.rs @@ -1,20 +1,21 @@ use crate::node::{Node, NodeType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { #[allow(dead_code)] - fn parse_at_rule_layer_list(&mut self) -> Result { + fn parse_at_rule_layer_list(&mut self) -> CssResult { let _children: Vec = Vec::new(); todo!(); } - fn parse_layer_query(&mut self) -> Result { + fn parse_layer_query(&mut self) -> CssResult { let _children: Vec = Vec::new(); todo!(); } - pub fn parse_at_rule_layer_prelude(&mut self) -> Result { + pub fn parse_at_rule_layer_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_layer_prelude"); let loc = self.tokenizer.current_location(); diff --git a/crates/gosub_css3/src/parser/at_rule/media.rs b/crates/gosub_css3/src/parser/at_rule/media.rs index 50e05f2cc..421d92263 100644 --- a/crates/gosub_css3/src/parser/at_rule/media.rs +++ b/crates/gosub_css3/src/parser/at_rule/media.rs @@ -1,9 +1,10 @@ use crate::node::{FeatureKind, Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::{CssError, CssResult}; impl Css3<'_> { - fn parse_media_read_term(&mut self) -> Result { + fn parse_media_read_term(&mut self) -> CssResult { self.consume_whitespace_comments(); let loc = self.tokenizer.current_location(); @@ -12,9 +13,7 @@ impl Css3<'_> { match t.token_type { TokenType::Ident(ident) => Ok(Node::new(NodeType::Ident { value: ident }, loc)), TokenType::Number(value) => Ok(Node::new(NodeType::Number { value }, loc)), - TokenType::Dimension { value, unit } => { - Ok(Node::new(NodeType::Dimension { value, unit }, loc)) - } + TokenType::Dimension { value, unit } => Ok(Node::new(NodeType::Dimension { value, unit }, loc)), TokenType::Function(name) => { let name = name.to_lowercase(); let args = self.parse_pseudo_function(name.as_str())?; @@ -28,14 +27,14 @@ impl Css3<'_> { loc, )) } - _ => Err(Error::new( - "Expected identifier, number, dimension, or ratio".to_string(), + _ => Err(CssError::with_location( + "Expected identifier, number, dimension, or ratio", loc, )), } } - fn parse_media_read_comparison(&mut self) -> Result { + fn parse_media_read_comparison(&mut self) -> CssResult { self.consume_whitespace_comments(); let loc = self.tokenizer.current_location(); @@ -55,10 +54,10 @@ impl Css3<'_> { return Ok(Node::new(NodeType::Operator(format!("{}", delim)), loc)); } - Err(Error::new("Expected comparison operator".to_string(), loc)) + Err(CssError::with_location("Expected comparison operator", loc)) } - pub fn parse_media_query_list(&mut self) -> Result { + pub fn parse_media_query_list(&mut self) -> CssResult { log::trace!("parse_media_query_list"); let loc = self.tokenizer.current_location(); @@ -78,15 +77,10 @@ impl Css3<'_> { } } - Ok(Node::new( - NodeType::MediaQueryList { - media_queries: queries, - }, - loc, - )) + Ok(Node::new(NodeType::MediaQueryList { media_queries: queries }, loc)) } - fn parse_media_feature_feature(&mut self, kind: FeatureKind) -> Result { + fn parse_media_feature_feature(&mut self, kind: FeatureKind) -> CssResult { log::trace!("parse_media_feature_feature"); let loc = self.tokenizer.current_location(); @@ -105,7 +99,7 @@ impl Css3<'_> { if t.token_type != TokenType::RParen { if !t.is_colon() { - return Err(Error::new("Expected colon".to_string(), t.location)); + return Err(CssError::with_location("Expected colon", t.location)); } self.consume_whitespace_comments(); @@ -130,8 +124,8 @@ impl Css3<'_> { )) } _ => { - return Err(Error::new( - "Expected identifier, number, dimension, or ratio".to_string(), + return Err(CssError::with_location( + "Expected identifier, number, dimension, or ratio", t.location, )); } @@ -147,7 +141,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::Feature { kind, name, value }, loc)) } - fn parse_media_feature_range(&mut self, _kind: FeatureKind) -> Result { + fn parse_media_feature_range(&mut self, _kind: FeatureKind) -> CssResult { log::trace!("parse_media_feature_range"); let loc = self.tokenizer.current_location(); @@ -181,7 +175,7 @@ impl Css3<'_> { )) } - pub fn parse_media_feature_or_range(&mut self, kind: FeatureKind) -> Result { + pub fn parse_media_feature_or_range(&mut self, kind: FeatureKind) -> CssResult { log::trace!("parse_media_feature_or_range"); let t = self.tokenizer.lookahead_sc(1); @@ -195,7 +189,7 @@ impl Css3<'_> { self.parse_media_feature_range(kind) } - pub fn parse_media_query(&mut self) -> Result { + pub fn parse_media_query(&mut self) -> CssResult { log::trace!("parse_media_query"); let loc = self.tokenizer.current_location(); @@ -228,7 +222,7 @@ impl Css3<'_> { match nt.token_type { TokenType::Ident(s) => { if s != "and" { - return Err(Error::new("Expected 'and'".to_string(), nt.location)); + return Err(CssError::with_location("Expected 'and'", t.location)); } self.consume_ident("and")?; @@ -238,8 +232,8 @@ impl Css3<'_> { // skip; } _ => { - return Err(Error::new( - "Expected identifier or parenthesis".to_string(), + return Err(CssError::with_location( + "Expected identifier or parenthesis", t.location, )); } @@ -255,8 +249,8 @@ impl Css3<'_> { // skip } _ => { - return Err(Error::new( - "Expected identifier or parenthesis".to_string(), + return Err(CssError::with_location( + "Expected identifier or parenthesis", t.location, )); } @@ -273,7 +267,7 @@ impl Css3<'_> { )) } - pub fn parse_at_rule_media_prelude(&mut self) -> Result { + pub fn parse_at_rule_media_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_media_prelude"); self.parse_media_query_list() diff --git a/crates/gosub_css3/src/parser/at_rule/nest.rs b/crates/gosub_css3/src/parser/at_rule/nest.rs index f1fd96c9a..a54420280 100644 --- a/crates/gosub_css3/src/parser/at_rule/nest.rs +++ b/crates/gosub_css3/src/parser/at_rule/nest.rs @@ -1,8 +1,9 @@ use crate::node::{Node, NodeType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_at_rule_nest_prelude(&mut self) -> Result { + pub fn parse_at_rule_nest_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_nest_prelude"); let loc = self.tokenizer.current_location(); diff --git a/crates/gosub_css3/src/parser/at_rule/page.rs b/crates/gosub_css3/src/parser/at_rule/page.rs index 86e04f669..cbbb4dd58 100644 --- a/crates/gosub_css3/src/parser/at_rule/page.rs +++ b/crates/gosub_css3/src/parser/at_rule/page.rs @@ -1,8 +1,9 @@ use crate::node::{Node, NodeType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_at_rule_page_prelude(&mut self) -> Result { + pub fn parse_at_rule_page_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_page_prelude"); let loc = self.tokenizer.current_location(); diff --git a/crates/gosub_css3/src/parser/at_rule/scope.rs b/crates/gosub_css3/src/parser/at_rule/scope.rs index af0f3a1e0..f129ff378 100644 --- a/crates/gosub_css3/src/parser/at_rule/scope.rs +++ b/crates/gosub_css3/src/parser/at_rule/scope.rs @@ -1,9 +1,10 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_at_rule_scope_prelude(&mut self) -> Result { + pub fn parse_at_rule_scope_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_scope_prelude"); let mut root = None; diff --git a/crates/gosub_css3/src/parser/at_rule/starting_style.rs b/crates/gosub_css3/src/parser/at_rule/starting_style.rs index ea5f3c1a9..0189fe084 100644 --- a/crates/gosub_css3/src/parser/at_rule/starting_style.rs +++ b/crates/gosub_css3/src/parser/at_rule/starting_style.rs @@ -1,8 +1,9 @@ use crate::node::Node; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_at_rule_starting_style_block(&mut self) -> Result { + pub fn parse_at_rule_starting_style_block(&mut self) -> CssResult { log::trace!("parse_at_rule_starting_style_block"); todo!(); } diff --git a/crates/gosub_css3/src/parser/at_rule/supports.rs b/crates/gosub_css3/src/parser/at_rule/supports.rs index dfae55621..903397347 100644 --- a/crates/gosub_css3/src/parser/at_rule/supports.rs +++ b/crates/gosub_css3/src/parser/at_rule/supports.rs @@ -1,8 +1,9 @@ use crate::node::{Node, NodeType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_at_rule_supports_prelude(&mut self) -> Result { + pub fn parse_at_rule_supports_prelude(&mut self) -> CssResult { log::trace!("parse_at_rule_supports_prelude"); let loc = self.tokenizer.current_location(); @@ -17,6 +18,7 @@ impl Css3<'_> { #[cfg(test)] mod tests { use crate::walker::Walker; + use crate::{CssOrigin, ParserConfig}; use gosub_shared::byte_stream::{ByteStream, Encoding}; #[test] @@ -25,7 +27,7 @@ mod tests { stream.read_from_str("(display: flex)", Some(Encoding::UTF8)); stream.close(); - let mut parser = crate::Css3::new(&mut stream); + let mut parser = crate::Css3::new(&mut stream, ParserConfig::default(), CssOrigin::User, ""); let node = parser.parse_at_rule_supports_prelude().unwrap(); let w = Walker::new(&node); diff --git a/crates/gosub_css3/src/parser/block.rs b/crates/gosub_css3/src/parser/block.rs index 656e107b7..22334aca4 100644 --- a/crates/gosub_css3/src/parser/block.rs +++ b/crates/gosub_css3/src/parser/block.rs @@ -1,6 +1,7 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::{CssError, CssResult}; #[derive(Debug, PartialEq)] pub enum BlockParseMode { @@ -9,12 +10,12 @@ pub enum BlockParseMode { } impl Css3<'_> { - fn parse_consume_rule(&mut self) -> Result, Error> { + fn parse_consume_rule(&mut self) -> CssResult> { log::trace!("parse_consume_rule"); self.parse_rule() } - fn parse_consume_declaration(&mut self) -> Result, Error> { + fn parse_consume_declaration(&mut self) -> CssResult> { log::trace!("parse_consume_declaration"); match self.parse_declaration()? { @@ -48,7 +49,7 @@ impl Css3<'_> { } } - pub fn parse_block(&mut self, mode: BlockParseMode) -> Result { + pub fn parse_block(&mut self, mode: BlockParseMode) -> CssResult { log::trace!("parse_block with parse mode: {:?}", mode); let loc = self.tokenizer.current_location(); @@ -62,7 +63,7 @@ impl Css3<'_> { // End the block self.tokenizer.reconsume(); - let n = Node::new(NodeType::Block { children }, t.location.clone()); + let n = Node::new(NodeType::Block { children }, t.location); return Ok(n); } TokenType::Whitespace(_) | TokenType::Comment(_) => { @@ -71,9 +72,7 @@ impl Css3<'_> { TokenType::AtKeyword(_) => { self.tokenizer.reconsume(); - if let Some(at_rule_node) = - self.parse_at_rule(mode == BlockParseMode::StyleBlock)? - { + if let Some(at_rule_node) = self.parse_at_rule(mode == BlockParseMode::StyleBlock)? { children.push(at_rule_node); } semicolon_seperated = false; @@ -85,8 +84,8 @@ impl Css3<'_> { _ => match mode { BlockParseMode::StyleBlock => { if !semicolon_seperated { - return Err(Error::new( - format!("Expected a ; got {:?}", t), + return Err(CssError::with_location( + format!("Expected a ; got {:?}", t).as_str(), self.tokenizer.current_location(), )); } diff --git a/crates/gosub_css3/src/parser/calc.rs b/crates/gosub_css3/src/parser/calc.rs index 74f3643d7..b9ae1d578 100644 --- a/crates/gosub_css3/src/parser/calc.rs +++ b/crates/gosub_css3/src/parser/calc.rs @@ -1,9 +1,10 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_calc(&mut self) -> Result { + pub fn parse_calc(&mut self) -> CssResult { log::trace!("parse_calc"); let loc = self.tokenizer.current_location(); @@ -13,7 +14,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::Calc { expr }, loc)) } - fn parse_calc_expr(&mut self) -> Result { + fn parse_calc_expr(&mut self) -> CssResult { log::trace!("parse_calc_expr"); let loc = self.tokenizer.current_location(); diff --git a/crates/gosub_css3/src/parser/combinator.rs b/crates/gosub_css3/src/parser/combinator.rs index e8613044e..2003c8663 100644 --- a/crates/gosub_css3/src/parser/combinator.rs +++ b/crates/gosub_css3/src/parser/combinator.rs @@ -1,9 +1,11 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_combinator(&mut self) -> Result { + pub fn parse_combinator(&mut self) -> CssResult { log::trace!("parse_combinator"); let t = self.consume_any()?; @@ -15,20 +17,18 @@ impl Css3<'_> { TokenType::Delim('/') => { let tn1 = self.tokenizer.lookahead(1); let tn2 = self.tokenizer.lookahead(2); - if tn1.token_type == TokenType::Ident("deep".to_string()) - && tn2.token_type == TokenType::Delim('/') - { + if tn1.token_type == TokenType::Ident("deep".to_string()) && tn2.token_type == TokenType::Delim('/') { "/deep/".to_string() } else { - return Err(Error::new( - format!("Unexpected token {:?}", tn1), + return Err(CssError::with_location( + format!("Unexpected token {:?}", tn1).as_str(), self.tokenizer.current_location(), )); } } _ => { - return Err(Error::new( - format!("Unexpected token {:?}", t), + return Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )); } diff --git a/crates/gosub_css3/src/parser/condition.rs b/crates/gosub_css3/src/parser/condition.rs index e53234e5a..95cadbe29 100644 --- a/crates/gosub_css3/src/parser/condition.rs +++ b/crates/gosub_css3/src/parser/condition.rs @@ -1,9 +1,11 @@ use crate::node::{FeatureKind, Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_condition(&mut self, kind: FeatureKind) -> Result { + pub fn parse_condition(&mut self, kind: FeatureKind) -> CssResult { log::trace!("parse_condition"); let loc = self.tokenizer.current_location(); @@ -55,7 +57,7 @@ impl Css3<'_> { } if list.is_empty() { - return Err(Error::new("Expected condition".to_string(), loc)); + return Err(CssError::with_location("Expected condition", loc)); } Ok(Node::new(NodeType::Condition { list }, loc)) diff --git a/crates/gosub_css3/src/parser/declaration.rs b/crates/gosub_css3/src/parser/declaration.rs index 4569ae8e4..f51c10245 100644 --- a/crates/gosub_css3/src/parser/declaration.rs +++ b/crates/gosub_css3/src/parser/declaration.rs @@ -1,9 +1,11 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_property_name(&mut self) -> Result { + pub fn parse_property_name(&mut self) -> CssResult { log::trace!("parse_property_name"); let t = self.consume_any()?; match t.token_type { @@ -27,14 +29,14 @@ impl Css3<'_> { match t.token_type { TokenType::Ident(value) => Ok(value), TokenType::Hash(value) => Ok(value), - _ => Err(Error::new( - format!("Unexpected token {:?}", t), + _ => Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - pub fn parse_declaration(&mut self) -> Result, Error> { + pub fn parse_declaration(&mut self) -> CssResult> { log::trace!("parse_declaration"); let result = self.parse_declaration_internal(); @@ -50,7 +52,7 @@ impl Css3<'_> { Ok(None) } - fn parse_declaration_internal(&mut self) -> Result { + fn parse_declaration_internal(&mut self) -> CssResult { let loc = self.tokenizer.current_location(); let mut important = false; @@ -69,8 +71,8 @@ impl Css3<'_> { let value = self.parse_value_sequence()?; if value.is_empty() { - return Err(Error::new( - "Expected value in declaration".to_string(), + return Err(CssError::with_location( + "Expected value in declaration", self.tokenizer.current_location(), )); } diff --git a/crates/gosub_css3/src/parser/feature_function.rs b/crates/gosub_css3/src/parser/feature_function.rs index 36ddfc6f1..db8a92166 100644 --- a/crates/gosub_css3/src/parser/feature_function.rs +++ b/crates/gosub_css3/src/parser/feature_function.rs @@ -1,13 +1,11 @@ use crate::node::{FeatureKind, Node, NodeType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_feature_function(&mut self, _kind: FeatureKind) -> Result { + pub fn parse_feature_function(&mut self, _kind: FeatureKind) -> CssResult { log::trace!("parse_feature_function"); - Ok(Node::new( - NodeType::FeatureFunction, - self.tokenizer.current_location(), - )) + Ok(Node::new(NodeType::FeatureFunction, self.tokenizer.current_location())) } } diff --git a/crates/gosub_css3/src/parser/function.rs b/crates/gosub_css3/src/parser/function.rs index 56ae01dfe..2f11af83b 100644 --- a/crates/gosub_css3/src/parser/function.rs +++ b/crates/gosub_css3/src/parser/function.rs @@ -1,14 +1,15 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - fn parse_function_arguments(&mut self) -> Result, Error> { + fn parse_function_arguments(&mut self) -> CssResult> { log::trace!("parse_function_arguments"); self.parse_value_sequence() } - pub fn parse_function(&mut self) -> Result { + pub fn parse_function(&mut self) -> CssResult { log::trace!("parse_function"); let loc = self.tokenizer.current_location(); diff --git a/crates/gosub_css3/src/parser/operator.rs b/crates/gosub_css3/src/parser/operator.rs index 073f9522e..a78d087c0 100644 --- a/crates/gosub_css3/src/parser/operator.rs +++ b/crates/gosub_css3/src/parser/operator.rs @@ -1,9 +1,11 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_operator(&mut self) -> Result { + pub fn parse_operator(&mut self) -> CssResult { log::trace!("parse_operator"); let loc = self.tokenizer.current_location(); @@ -18,8 +20,8 @@ impl Css3<'_> { } } - Err(Error::new( - format!("Expected operator, got {:?}", operator), + Err(CssError::with_location( + format!("Expected operator, got {:?}", operator).as_str(), self.tokenizer.current_location(), )) } diff --git a/crates/gosub_css3/src/parser/pseudo.rs b/crates/gosub_css3/src/parser/pseudo.rs index 8e5ff612e..2651ac211 100644 --- a/crates/gosub_css3/src/parser/pseudo.rs +++ b/crates/gosub_css3/src/parser/pseudo.rs @@ -1,20 +1,22 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - fn parse_pseudo_function_selector_list(&mut self) -> Result { + fn parse_pseudo_function_selector_list(&mut self) -> CssResult { log::trace!("parse_pseudo_function_selector_list"); self.parse_selector_list() } - fn parse_pseudo_function_selector(&mut self) -> Result { + fn parse_pseudo_function_selector(&mut self) -> CssResult { log::trace!("parse_pseudo_function_selector"); self.parse_selector() } - fn parse_pseudo_function_ident_list(&mut self) -> Result { + fn parse_pseudo_function_ident_list(&mut self) -> CssResult { log::trace!("parse_pseudo_function_ident_list"); let loc = self.tokenizer.current_location(); @@ -24,7 +26,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::Ident { value }, loc)) } - fn parse_pseudo_function_nth(&mut self) -> Result { + fn parse_pseudo_function_nth(&mut self) -> CssResult { log::trace!("parse_pseudo_function_nth"); self.consume_whitespace_comments(); @@ -39,14 +41,14 @@ impl Css3<'_> { a: "2".into(), b: "1".into(), }, - loc.clone(), + loc, ), TokenType::Ident(value) if value == "even" => Node::new( NodeType::AnPlusB { a: "2".into(), b: "0".into(), }, - loc.clone(), + loc, ), TokenType::Ident(_) => { self.tokenizer.reconsume(); @@ -56,10 +58,10 @@ impl Css3<'_> { self.tokenizer.reconsume(); self.parse_anplusb()? } - TokenType::Number(value) => Node::new(NodeType::Number { value }, loc.clone()), + TokenType::Number(value) => Node::new(NodeType::Number { value }, loc), _ => { - return Err(Error::new( - format!("Unexpected token {:?}", self.tokenizer.lookahead(0)), + return Err(CssError::with_location( + format!("Unexpected token {:?}", self.tokenizer.lookahead(0)).as_str(), self.tokenizer.current_location(), )); } @@ -76,10 +78,10 @@ impl Css3<'_> { } } - Ok(Node::new(NodeType::Nth { nth, selector }, loc.clone())) + Ok(Node::new(NodeType::Nth { nth, selector }, loc)) } - pub(crate) fn parse_pseudo_function(&mut self, name: &str) -> Result { + pub(crate) fn parse_pseudo_function(&mut self, name: &str) -> CssResult { log::trace!("parse_pseudo_function"); match name { "dir" => self.parse_pseudo_function_ident_list(), @@ -98,8 +100,8 @@ impl Css3<'_> { "slotted" => self.parse_pseudo_function_selector(), "host" => self.parse_pseudo_function_selector(), "host-context" => self.parse_pseudo_function_selector(), - _ => Err(Error::new( - format!("Unexpected pseudo function {:?}", name), + _ => Err(CssError::with_location( + format!("Unexpected pseudo function {:?}", name).as_str(), self.tokenizer.current_location(), )), } diff --git a/crates/gosub_css3/src/parser/rule.rs b/crates/gosub_css3/src/parser/rule.rs index c76fa1049..72b7d1eb2 100644 --- a/crates/gosub_css3/src/parser/rule.rs +++ b/crates/gosub_css3/src/parser/rule.rs @@ -1,13 +1,14 @@ use crate::node::{Node, NodeType}; use crate::parser::block::BlockParseMode; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { // Either the rule parsing succeeds as a whole, or not. When not a valid rule is found, we // return None if the config.ignore_errors is set to true, otherwise this will return an Err // and is handled by the caller - pub fn parse_rule(&mut self) -> Result, Error> { + pub fn parse_rule(&mut self) -> CssResult> { log::trace!("parse_rule"); let result = self.parse_rule_internal(); @@ -24,7 +25,7 @@ impl Css3<'_> { Ok(None) } - fn parse_rule_internal(&mut self) -> Result { + fn parse_rule_internal(&mut self) -> CssResult { let loc = self.tokenizer.current_location(); let prelude = self.parse_selector_list()?; @@ -49,6 +50,7 @@ impl Css3<'_> { #[cfg(test)] mod tests { use crate::walker::Walker; + use crate::{CssOrigin, ParserConfig}; use gosub_shared::byte_stream::{ByteStream, Encoding}; macro_rules! test { @@ -57,7 +59,7 @@ mod tests { stream.read_from_str($input, Some(Encoding::UTF8)); stream.close(); - let mut parser = crate::Css3::new(&mut stream); + let mut parser = crate::Css3::new(&mut stream, ParserConfig::default(), CssOrigin::User, ""); let result = parser.$func().unwrap().unwrap(); let w = Walker::new(&result); diff --git a/crates/gosub_css3/src/parser/selector.rs b/crates/gosub_css3/src/parser/selector.rs index 90535e92f..97b44bf2f 100644 --- a/crates/gosub_css3/src/parser/selector.rs +++ b/crates/gosub_css3/src/parser/selector.rs @@ -1,9 +1,11 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - fn parse_attribute_operator(&mut self) -> Result { + fn parse_attribute_operator(&mut self) -> CssResult { log::trace!("parse_attribute_operator"); let mut value = String::new(); @@ -17,8 +19,8 @@ impl Css3<'_> { _ => { self.tokenizer.reconsume(); - return Err(Error::new( - format!("Expected attribute operator, got {:?}", c), + return Err(CssError::with_location( + format!("Expected attribute operator, got {:?}", c).as_str(), loc, )); } @@ -32,7 +34,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::Operator(value), loc)) } - fn parse_class_selector(&mut self) -> Result { + fn parse_class_selector(&mut self) -> CssResult { log::trace!("parse_class_selector"); let loc = self.tokenizer.current_location(); @@ -44,7 +46,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::ClassSelector { value }, loc)) } - fn parse_nesting_selector(&mut self) -> Result { + fn parse_nesting_selector(&mut self) -> CssResult { log::trace!("parse_nesting_selector"); let loc = self.tokenizer.current_location(); @@ -54,7 +56,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::NestingSelector, loc)) } - fn parse_type_selector_ident_or_asterisk(&mut self) -> Result { + fn parse_type_selector_ident_or_asterisk(&mut self) -> CssResult { let t = self.tokenizer.lookahead(0); match t.token_type { TokenType::Ident(value) => { @@ -65,14 +67,14 @@ impl Css3<'_> { self.tokenizer.consume(); Ok("*".to_string()) } - _ => Err(Error::new( - format!("Unexpected token {:?}", t), + _ => Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )), } } - fn parse_type_selector(&mut self) -> Result { + fn parse_type_selector(&mut self) -> CssResult { log::trace!("parse_type_selector"); let loc = self.tokenizer.current_location(); @@ -108,7 +110,7 @@ impl Css3<'_> { )) } - fn parse_attribute_selector(&mut self) -> Result { + fn parse_attribute_selector(&mut self) -> CssResult { log::trace!("parse_attribute_selector"); let loc = self.tokenizer.current_location(); @@ -137,8 +139,8 @@ impl Css3<'_> { } else if t.is_ident() { value = self.consume_any_ident()?; } else { - return Err(Error::new( - format!("Unexpected token {:?}", t), + return Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )); } @@ -167,7 +169,7 @@ impl Css3<'_> { )) } - fn parse_id_selector(&mut self) -> Result { + fn parse_id_selector(&mut self) -> CssResult { log::trace!("parse_id_selector"); let loc = self.tokenizer.current_location(); @@ -178,8 +180,8 @@ impl Css3<'_> { let value = match t.token_type { TokenType::Ident(s) => s, _ => { - return Err(Error::new( - format!("Unexpected token {:?}", t), + return Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )); } @@ -188,7 +190,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::IdSelector { value }, loc)) } - fn parse_pseudo_element_selector(&mut self) -> Result { + fn parse_pseudo_element_selector(&mut self) -> CssResult { log::trace!("parse_pseudo_element_selector"); let loc = self.tokenizer.current_location(); @@ -200,8 +202,8 @@ impl Css3<'_> { let value = if t.is_ident() { self.consume_any_ident()? } else { - return Err(Error::new( - format!("Unexpected token {:?}", t), + return Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )); }; @@ -209,7 +211,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::PseudoElementSelector { value }, loc)) } - fn parse_pseudo_selector(&mut self) -> Result { + fn parse_pseudo_selector(&mut self) -> CssResult { log::trace!("parse_pseudo_selector"); let loc = self.tokenizer.current_location(); @@ -233,8 +235,8 @@ impl Css3<'_> { ) } _ => { - return Err(Error::new( - format!("Unexpected token {:?}", t), + return Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )); } @@ -243,7 +245,7 @@ impl Css3<'_> { Ok(Node::new(NodeType::PseudoClassSelector { value }, loc)) } - pub fn parse_selector(&mut self) -> Result { + pub fn parse_selector(&mut self) -> CssResult { log::trace!("parse_selector"); let loc = self.tokenizer.current_location(); @@ -253,7 +255,7 @@ impl Css3<'_> { // When true, we have encountered a space which means we need to emit a descendant combinator let mut space = false; - let mut whitespace_location = loc.clone(); + let mut whitespace_location = loc; let mut skip_space = false; @@ -273,7 +275,7 @@ impl Css3<'_> { if t.is_whitespace() { // on whitespace for selector - whitespace_location = t.location.clone(); + whitespace_location = t.location; space = true; continue; } @@ -299,18 +301,11 @@ impl Css3<'_> { TokenType::Number(value) => Node::new(NodeType::Number { value }, t.location), - TokenType::Percentage(value) => { - Node::new(NodeType::Percentage { value }, t.location) - } + TokenType::Percentage(value) => Node::new(NodeType::Percentage { value }, t.location), - TokenType::Dimension { value, unit } => { - Node::new(NodeType::Dimension { value, unit }, t.location) - } + TokenType::Dimension { value, unit } => Node::new(NodeType::Dimension { value, unit }, t.location), - TokenType::Delim('+') - | TokenType::Delim('>') - | TokenType::Delim('~') - | TokenType::Delim('/') => { + TokenType::Delim('+') | TokenType::Delim('>') | TokenType::Delim('~') | TokenType::Delim('/') => { // Dont add descendant combinator since we are now adding another one space = false; @@ -347,12 +342,7 @@ impl Css3<'_> { if space { // Detected a space previously, so we need to emit a descendant combinator - let node = Node::new( - NodeType::Combinator { - value: " ".to_string(), - }, - whitespace_location.clone(), - ); + let node = Node::new(NodeType::Combinator { value: " ".to_string() }, whitespace_location); // insert before the last added node children.push(node); space = false; diff --git a/crates/gosub_css3/src/parser/selector_list.rs b/crates/gosub_css3/src/parser/selector_list.rs index 1e068c640..3f2c92fbb 100644 --- a/crates/gosub_css3/src/parser/selector_list.rs +++ b/crates/gosub_css3/src/parser/selector_list.rs @@ -1,8 +1,9 @@ use crate::node::{Node, NodeType}; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_selector_list(&mut self) -> Result { + pub fn parse_selector_list(&mut self) -> CssResult { log::trace!("parse_selector_list"); let loc = self.tokenizer.current_location(); diff --git a/crates/gosub_css3/src/parser/stylesheet.rs b/crates/gosub_css3/src/parser/stylesheet.rs index 8bb2eb42c..741946566 100644 --- a/crates/gosub_css3/src/parser/stylesheet.rs +++ b/crates/gosub_css3/src/parser/stylesheet.rs @@ -1,9 +1,10 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_stylesheet(&mut self) -> Result, Error> { + pub fn parse_stylesheet_internal(&mut self) -> CssResult> { log::trace!("parse_stylesheet"); let loc = self.tokenizer.current_location(); @@ -18,17 +19,14 @@ impl Css3<'_> { TokenType::Whitespace(_) => {} TokenType::Comment(comment) => { if comment.chars().nth(2) == Some('!') { - children.push(Node::new( - NodeType::Comment { value: comment }, - t.location.clone(), - )); + children.push(Node::new(NodeType::Comment { value: comment }, t.location)); } } TokenType::Cdo => { - children.push(Node::new(NodeType::Cdo, t.location.clone())); + children.push(Node::new(NodeType::Cdo, t.location)); } TokenType::Cdc => { - children.push(Node::new(NodeType::Cdc, t.location.clone())); + children.push(Node::new(NodeType::Cdc, t.location)); } TokenType::AtKeyword(_keyword) => { self.tokenizer.reconsume(); diff --git a/crates/gosub_css3/src/parser/url.rs b/crates/gosub_css3/src/parser/url.rs index 979ea42de..71e400461 100644 --- a/crates/gosub_css3/src/parser/url.rs +++ b/crates/gosub_css3/src/parser/url.rs @@ -1,17 +1,19 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_url(&mut self) -> Result { + pub fn parse_url(&mut self) -> CssResult { log::trace!("parse_url"); let loc = self.tokenizer.current_location(); let name = self.consume_function()?; if name.to_ascii_lowercase() != "url" { - return Err(Error::new( - format!("Expected url, got {:?}", name), + return Err(CssError::with_location( + format!("Expected url, got {:?}", name).as_str(), self.tokenizer.current_location(), )); } @@ -20,10 +22,10 @@ impl Css3<'_> { let url = match t.token_type { TokenType::QuotedString(url) => url, _ => { - return Err(Error::new( - format!("Expected url, got {:?}", t), + return Err(CssError::with_location( + format!("Expected url, got {:?}", t).as_str(), self.tokenizer.current_location(), - )) + )); } }; @@ -36,6 +38,7 @@ impl Css3<'_> { #[cfg(test)] mod tests { use crate::walker::Walker; + use crate::{CssOrigin, ParserConfig}; use gosub_shared::byte_stream::{ByteStream, Encoding}; macro_rules! test { @@ -44,7 +47,7 @@ mod tests { stream.read_from_str($input, Some(Encoding::UTF8)); stream.close(); - let mut parser = crate::Css3::new(&mut stream); + let mut parser = crate::Css3::new(&mut stream, ParserConfig::default(), CssOrigin::User, ""); let result = parser.$func().unwrap(); let w = Walker::new(&result); @@ -58,7 +61,7 @@ mod tests { // stream.read_from_str($input, Some(Encoding::UTF8)); // stream.close(); // - // let mut parser = crate::Css3::new(&mut stream); + // let mut parser = crate::Css3::new(&mut stream, Default::default(), Default::default(), ""); // let result = parser.$func(); // // assert_eq!(true, result.is_err()); diff --git a/crates/gosub_css3/src/parser/value.rs b/crates/gosub_css3/src/parser/value.rs index d7697b3c0..7c5b1567d 100644 --- a/crates/gosub_css3/src/parser/value.rs +++ b/crates/gosub_css3/src/parser/value.rs @@ -1,9 +1,11 @@ use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; -use crate::{Css3, Error}; +use crate::Css3; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; impl Css3<'_> { - pub fn parse_value_sequence(&mut self) -> Result, Error> { + pub fn parse_value_sequence(&mut self) -> CssResult> { log::trace!("parse_value_sequence"); let mut children = Vec::new(); @@ -38,7 +40,7 @@ impl Css3<'_> { // none: no value is found (but this is not an error) // err: // parsing went wrong - fn parse_value(&mut self) -> Result, Error> { + fn parse_value(&mut self) -> CssResult> { log::trace!("parse_value"); let t = self.consume_any()?; @@ -51,8 +53,8 @@ impl Css3<'_> { let node = Node::new(NodeType::Operator(",".into()), t.location); Ok(Some(node)) } - TokenType::LBracket => Err(Error::new( - "Unexpected token [".to_string(), + TokenType::LBracket => Err(CssError::with_location( + "Unexpected token [", self.tokenizer.current_location(), )), TokenType::QuotedString(value) => { @@ -104,9 +106,7 @@ impl Css3<'_> { return Ok(Some(n)); } - if !self.allow_values_in_argument_list.is_empty() - && self.tokenizer.lookahead(0).is_delim('=') - { + if !self.allow_values_in_argument_list.is_empty() && self.tokenizer.lookahead(0).is_delim('=') { self.consume_delim('=')?; let t = self.consume_any()?; let node = match t.token_type { @@ -124,16 +124,12 @@ impl Css3<'_> { }, t.location, ), - TokenType::Ident(default_value) => Node::new( - NodeType::MSIdent { - value, - default_value, - }, - t.location, - ), + TokenType::Ident(default_value) => { + Node::new(NodeType::MSIdent { value, default_value }, t.location) + } _ => { - return Err(Error::new( - format!("Expected number or ident, got {:?}", t), + return Err(CssError::with_location( + format!("Expected number or ident, got {:?}", t).as_str(), self.tokenizer.current_location(), )) } @@ -156,8 +152,8 @@ impl Css3<'_> { let node = self.parse_operator()?; Ok(Some(node)) } - '#' => Err(Error::new( - format!("Unexpected token {:?}", t), + '#' => Err(CssError::with_location( + format!("Unexpected token {:?}", t).as_str(), self.tokenizer.current_location(), )), _ => { diff --git a/crates/gosub_css3/src/stylesheet.rs b/crates/gosub_css3/src/stylesheet.rs index 1a4a40a8b..54ca3a00f 100644 --- a/crates/gosub_css3/src/stylesheet.rs +++ b/crates/gosub_css3/src/stylesheet.rs @@ -1,33 +1,120 @@ use core::fmt::Debug; +use gosub_shared::byte_stream::Location; +use gosub_shared::errors::CssError; +use gosub_shared::errors::CssResult; +use gosub_shared::traits::css3::CssOrigin; use std::cmp::Ordering; use std::fmt::Display; -use anyhow::anyhow; +use crate::colors::RgbColor; -use gosub_shared::types::Result; +/// Severity of a CSS error +#[derive(Debug, PartialEq)] +pub enum Severity { + /// A critical error that will prevent the stylesheet from being applied + Error, + /// A warning that will be displayed but will not prevent the stylesheet from being applied + Warning, + /// An information message that can be displayed to the user + Info, +} -use crate::colors::RgbColor; +impl Display for Severity { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Severity::Error => write!(f, "Error"), + Severity::Warning => write!(f, "Warning"), + Severity::Info => write!(f, "Info"), + } + } +} + +/// Defines a CSS log during +#[derive(PartialEq)] +pub struct CssLog { + /// Severity of the error + pub severity: Severity, + /// Error message + pub message: String, + /// Location of the error + pub location: Location, +} + +impl CssLog { + pub fn log(severity: Severity, message: &str, location: Location) -> Self { + Self { + severity, + message: message.to_string(), + location, + } + } + + pub fn error(message: &str, location: Location) -> Self { + Self { + severity: Severity::Error, + message: message.to_string(), + location, + } + } + + pub fn warn(message: &str, location: Location) -> Self { + Self { + severity: Severity::Warning, + message: message.to_string(), + location, + } + } + + pub fn info(message: &str, location: Location) -> Self { + Self { + severity: Severity::Info, + message: message.to_string(), + location, + } + } +} + +impl Display for CssLog { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{}] ({}:{}): {}", + self.severity, self.location.line, self.location.column, self.message + ) + } +} + +impl Debug for CssLog { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "[{}] ({}:{}): {}", + self.severity, self.location.line, self.location.column, self.message + ) + } +} /// Defines a complete stylesheet with all its rules and the location where it was found -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq)] pub struct CssStylesheet { /// List of rules found in this stylesheet pub rules: Vec, /// Origin of the stylesheet (user agent, author, user) pub origin: CssOrigin, /// Url or file path where the stylesheet was found - pub location: String, + pub url: String, + /// Any issues during parsing of the stylesheet + pub parse_log: Vec, } -/// Defines the origin of the stylesheet (or declaration) -#[derive(Debug, PartialEq, Clone)] -pub enum CssOrigin { - /// Browser/user agent defined stylesheets - UserAgent, - /// Author defined stylesheets that are linked or embedded in the HTML files - Author, - /// User defined stylesheets that will override the author and user agent stylesheets (for instance, custom user styles or extensions) - User, +impl gosub_shared::traits::css3::CssStylesheet for CssStylesheet { + fn origin(&self) -> CssOrigin { + self.origin + } + + fn url(&self) -> &str { + &self.url + } } /// A CSS rule, which contains a list of selectors and a list of declarations @@ -54,7 +141,7 @@ impl CssRule { pub struct CssDeclaration { // Css property color pub property: String, - // Raw values of the declaration. It is not calculated or converted in any way (ie: "red", "50px" etc) + // Raw values of the declaration. It is not calculated or converted in any way (ie: "red", "50px" etc.) // There can be multiple values (ie: "1px solid black" are split into 3 values) pub value: Vec, // ie: !important @@ -352,7 +439,7 @@ impl CssValue { } /// Converts a CSS AST node to a CSS value - pub fn parse_ast_node(node: &crate::node::Node) -> Result { + pub fn parse_ast_node(node: &crate::node::Node) -> CssResult { match *node.node_type.clone() { crate::node::NodeType::Ident { value } => Ok(CssValue::String(value)), crate::node::NodeType::Number { value } => { @@ -373,13 +460,10 @@ impl CssValue { Ok(CssValue::String(value)) } crate::node::NodeType::Operator(_) => Ok(CssValue::None), - crate::node::NodeType::Calc { .. } => { - Ok(CssValue::Function("calc".to_string(), vec![])) + crate::node::NodeType::Calc { .. } => Ok(CssValue::Function("calc".to_string(), vec![])), + crate::node::NodeType::Url { url } => { + Ok(CssValue::Function("url".to_string(), vec![CssValue::String(url)])) } - crate::node::NodeType::Url { url } => Ok(CssValue::Function( - "url".to_string(), - vec![CssValue::String(url)], - )), crate::node::NodeType::Function { name, arguments } => { let mut list = vec![]; for node in arguments.iter() { @@ -390,15 +474,14 @@ impl CssValue { } Ok(CssValue::Function(name, list)) } - _ => Err(anyhow!(format!( - "Cannot convert node to CssValue: {:?}", - node - ))), + _ => Err(CssError::new( + format!("Cannot convert node to CssValue: {:?}", node).as_str(), + )), } } /// Parses a string into a CSS value or list of css values - pub fn parse_str(value: &str) -> Result { + pub fn parse_str(value: &str) -> CssResult { match value { "initial" => return Ok(CssValue::Initial), "inherit" => return Ok(CssValue::Inherit), @@ -444,6 +527,64 @@ impl CssValue { } } +impl gosub_shared::traits::css3::CssValue for CssValue { + fn unit_to_px(&self) -> f32 { + self.unit_to_px() + } + + fn as_string(&self) -> Option<&str> { + if let CssValue::String(str) = &self { + Some(str) + } else { + None + } + } + + fn as_percentage(&self) -> Option { + if let CssValue::Percentage(percent) = &self { + Some(*percent) + } else { + None + } + } + + fn as_unit(&self) -> Option<(f32, &str)> { + if let CssValue::Unit(value, unit) = &self { + Some((*value, unit)) + } else { + None + } + } + + fn as_color(&self) -> Option<(f32, f32, f32, f32)> { + if let CssValue::Color(color) = &self { + Some((color.r, color.g, color.b, color.a)) + } else { + None + } + } + + fn as_number(&self) -> Option { + if let CssValue::Number(num) = &self { + Some(*num) + } else { + None + } + } + + fn as_list(&self) -> Option> { + if let CssValue::List(list) = &self { + Some(list.to_vec()) + } else { + None + } + } + + fn is_none(&self) -> bool { + matches!(self, CssValue::None) + } +} + #[cfg(test)] mod test { use std::vec; diff --git a/crates/gosub_css3/src/system.rs b/crates/gosub_css3/src/system.rs new file mode 100644 index 000000000..22b07f32f --- /dev/null +++ b/crates/gosub_css3/src/system.rs @@ -0,0 +1,248 @@ +use crate::functions::attr::resolve_attr; +use crate::functions::calc::resolve_calc; +use crate::functions::var::resolve_var; +use crate::matcher::property_definitions::get_css_definitions; +use crate::matcher::shorthands::FixList; +use crate::matcher::styling::{match_selector, CssProperties, CssProperty, DeclarationProperty}; +use crate::stylesheet::{CssDeclaration, CssValue, Specificity}; +use crate::{load_default_useragent_stylesheet, Css3}; +use gosub_shared::document::DocumentHandle; +use gosub_shared::errors::CssResult; +use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::{CssOrigin, CssPropertyMap, CssSystem}; +use gosub_shared::traits::document::Document; +use gosub_shared::traits::node::{ElementDataType, Node, TextDataType}; +use gosub_shared::traits::render_tree::{RenderTree, RenderTreeNode}; +use gosub_shared::traits::ParserConfig; +use log::warn; + +#[derive(Debug, Clone)] +pub struct Css3System; + +impl CssSystem for Css3System { + type Stylesheet = crate::stylesheet::CssStylesheet; + + type PropertyMap = CssProperties; + + type Property = CssProperty; + + fn parse_str(str: &str, config: ParserConfig, origin: CssOrigin, url: &str) -> CssResult { + Css3::parse_str(str, config, origin, url) + } + + fn properties_from_node>( + node: &D::Node, + sheets: &[Self::Stylesheet], + handle: DocumentHandle, + id: NodeId, + ) -> Option { + let mut css_map_entry = CssProperties::new(); + + if node_is_unrenderable::(node) { + return None; + } + + let definitions = get_css_definitions(); + + let mut fix_list = FixList::new(); + + for sheet in sheets { + for rule in &sheet.rules { + for selector in rule.selectors().iter() { + let (matched, specificity) = match_selector(DocumentHandle::clone(&handle), id, selector); + + if !matched { + continue; + } + + // Selector matched, so we add all declared values to the map + for declaration in rule.declarations().iter() { + // Step 1: find the property in our CSS definition list + let Some(definition) = definitions.find_property(&declaration.property) else { + // If not found, we skip this declaration + warn!("Definition is not found for property {:?}", declaration.property); + continue; + }; + + let value = resolve_functions(&declaration.value, node, handle.clone()); + + // Check if the declaration matches the definition and return the "expanded" order + let res = definition.matches_and_shorthands(&value, &mut fix_list); + if !res { + warn!("Declaration does not match definition: {:?}", declaration); + continue; + } + + // create property for the given values + let property_name = declaration.property.clone(); + let decl = CssDeclaration { + property: property_name.to_string(), + value, + important: declaration.important, + }; + + add_property_to_map(&mut css_map_entry, sheet, specificity.clone(), &decl); + } + } + } + } + + fix_list.resolve_nested(definitions); + + fix_list.apply(&mut css_map_entry); + + Some(css_map_entry) + } + + fn inheritance>(tree: &mut T) { + Self::resolve_inheritance(tree, tree.root(), &Vec::new()); + } + + fn load_default_useragent_stylesheet() -> Self::Stylesheet { + load_default_useragent_stylesheet() + } +} + +impl Css3System { + fn resolve_inheritance>( + tree: &mut T, + node_id: T::NodeId, + inherit_props: &Vec<(String, CssValue)>, + ) { + let Some(current_node) = tree.get_node_mut(node_id) else { + return; + }; + + for prop in inherit_props { + let mut p = CssProperty::new(prop.0.as_str()); + + p.inherited = prop.1.clone(); + + current_node.props_mut().insert_inherited(prop.0.as_str(), p); + } + + let mut inherit_props = inherit_props.clone(); + + 'props: for (name, prop) in &mut current_node.props_mut().iter_mut() { + prop.compute_value(); + + let value = prop.actual.clone(); + + if prop_is_inherit(name) { + for (k, v) in &mut inherit_props { + if k == name { + *v = value; + continue 'props; + } + } + + inherit_props.push((name.to_owned(), value)); + } + } + + let Some(children) = tree.get_children(node_id) else { + return; + }; + + for child in children { + Self::resolve_inheritance(tree, child, &inherit_props); + } + } +} + +pub fn prop_is_inherit(name: &str) -> bool { + get_css_definitions() + .find_property(name) + .map(|def| def.inherited) + .unwrap_or(false) +} + +pub fn add_property_to_map( + css_map_entry: &mut CssProperties, + sheet: &crate::stylesheet::CssStylesheet, + specificity: Specificity, + declaration: &CssDeclaration, +) { + let property_name = declaration.property.clone(); + // let entry = CssProperty::new(property_name.as_str()); + + // If the property is a shorthand css property, we need fetch the individual properties + // It's possible that need to recurse here as these individual properties can be shorthand as well + // if entry.is_shorthand() { + // for property_name in entry.get_props_from_shorthand() { + // let decl = CssDeclaration { + // property: property_name.to_string(), + // value: declaration.value.clone(), + // important: declaration.important, + // }; + // + // add_property_to_map(css_map_entry, sheet, selector, &decl); + // } + // } + // + let declaration = DeclarationProperty { + // @todo: this seems wrong. We only get the first values from the declared values + value: declaration.value.first().unwrap().clone(), + origin: sheet.origin, + important: declaration.important, + location: sheet.url.clone(), + specificity, + }; + + if let std::collections::hash_map::Entry::Vacant(e) = css_map_entry.properties.entry(property_name.clone()) { + // Generate new property in the css map + let mut entry = CssProperty::new(property_name.as_str()); + entry.declared.push(declaration); + e.insert(entry); + } else { + // Just add the declaration to the existing property + let entry = css_map_entry.properties.get_mut(&property_name).unwrap(); + entry.declared.push(declaration); + } +} + +pub fn node_is_unrenderable, C: CssSystem>(node: &D::Node) -> bool { + // There are more elements that are not renderable, but for now we only remove the most common ones + + const REMOVABLE_ELEMENTS: [&str; 6] = ["head", "script", "style", "svg", "noscript", "title"]; + + if let Some(element_data) = node.get_element_data() { + if REMOVABLE_ELEMENTS.contains(&element_data.name()) { + return true; + } + } + + if let Some(text_data) = &node.get_text_data() { + if text_data.value().chars().all(|c| c.is_whitespace()) { + return true; + } + } + + false +} + +pub fn resolve_functions, C: CssSystem>( + value: &[CssValue], + node: &D::Node, + handle: DocumentHandle, +) -> Vec { + let mut result = Vec::with_capacity(value.len()); //TODO: we could give it a &mut Vec and reuse the allocation + + for val in value { + match val { + CssValue::Function(func, values) => { + let resolved = match func.as_str() { + "calc" => resolve_calc(values), + "attr" => resolve_attr(values, node), + "var" => resolve_var(values, &*handle.get(), node), + _ => vec![val.clone()], + }; + + result.extend(resolved); + } + _ => result.push(val.clone()), + } + } + + result +} diff --git a/crates/gosub_css3/src/tokenizer.rs b/crates/gosub_css3/src/tokenizer.rs index 8dce822a8..4925cf6e8 100644 --- a/crates/gosub_css3/src/tokenizer.rs +++ b/crates/gosub_css3/src/tokenizer.rs @@ -95,10 +95,7 @@ impl Debug for Token { impl Token { /// Returns a new token for the given type on the given location fn new(token_type: TokenType, location: Location) -> Token { - Token { - token_type, - location, - } + Token { token_type, location } } fn new_delim(c: char, location: Location) -> Token { @@ -256,7 +253,7 @@ impl<'stream> Tokenizer<'stream> { /// Returns the current location (line/col) of the tokenizer pub fn current_location(&self) -> Location { - self.location_handler.cur_location.clone() + self.location_handler.cur_location } /// Returns true when there is no next element, and the stream is closed @@ -602,9 +599,7 @@ impl<'stream> Tokenizer<'stream> { value.push_str(&self.consume_digits()); - if self.current_char() == Ch('.') - && matches!(self.stream.look_ahead(1), Ch(c) if c.is_numeric()) - { + if self.current_char() == Ch('.') && matches!(self.stream.look_ahead(1), Ch(c) if c.is_numeric()) { value.push_str(&self.consume_chars(2)); // type should be "number" @@ -747,9 +742,7 @@ impl<'stream> Tokenizer<'stream> { // todo: look for better implementation if let Some(char) = char::from_u32(as_u32) { - if char == get_unicode_char(&UnicodeChar::Null) - || char >= get_unicode_char(&UnicodeChar::MaxAllowed) - { + if char == get_unicode_char(&UnicodeChar::Null) || char >= get_unicode_char(&UnicodeChar::MaxAllowed) { return default_char; } @@ -842,8 +835,7 @@ impl<'stream> Tokenizer<'stream> { /// def: [non-printable code point](https://www.w3.org/TR/css-syntax-3/#non-printable-code-point) fn is_non_printable_char(&self) -> bool { if let Ch(char) = self.current_char() { - (char >= get_unicode_char(&UnicodeChar::Null) - && char <= get_unicode_char(&UnicodeChar::Backspace)) + (char >= get_unicode_char(&UnicodeChar::Null) && char <= get_unicode_char(&UnicodeChar::Backspace)) || (char >= get_unicode_char(&UnicodeChar::ShiftOut) && char <= get_unicode_char(&UnicodeChar::InformationSeparatorOne)) || char == get_unicode_char(&UnicodeChar::Tab) @@ -867,9 +859,7 @@ impl<'stream> Tokenizer<'stream> { let second = self.stream.look_ahead(start + 1); if first == Ch('-') { - return self.is_ident_start(second.into()) - || second == Ch('-') - || self.is_start_of_escape(start + 1); + return self.is_ident_start(second.into()) || second == Ch('-') || self.is_start_of_escape(start + 1); } if first == Ch('\\') { @@ -885,8 +875,7 @@ impl<'stream> Tokenizer<'stream> { let last = self.stream.look_ahead(start + 2); // e.g. +1, -1, +.1, -0.01 - matches!(current, Ch('+' | '-')) - && ((next == Ch('.') && last.is_numeric()) || next.is_numeric()) + matches!(current, Ch('+' | '-')) && ((next == Ch('.') && last.is_numeric()) || next.is_numeric()) } fn is_any_of(&self, chars: Vec) -> bool { @@ -990,9 +979,7 @@ mod test { let mut tokenizer = Tokenizer::new(&mut chars, Location::default()); for (raw_num, num_token) in num_tokens { - tokenizer - .stream - .read_from_str(raw_num, Some(Encoding::UTF8)); + tokenizer.stream.read_from_str(raw_num, Some(Encoding::UTF8)); assert_eq!(tokenizer.consume_number(), num_token); } } @@ -1012,9 +999,7 @@ mod test { let mut tokenizer = Tokenizer::new(&mut chars, Location::default()); for (raw_ident, ident_tokens) in ident_tokens { - tokenizer - .stream - .read_from_str(raw_ident, Some(Encoding::UTF8)); + tokenizer.stream.read_from_str(raw_ident, Some(Encoding::UTF8)); assert_eq!(tokenizer.consume_ident(), ident_tokens); } @@ -1028,26 +1013,15 @@ mod test { let escaped_chars = vec![ ("\\005F ", get_unicode_char(&UnicodeChar::LowLine)), ("\\2A", '*'), - ( - "\\000000 ", - get_unicode_char(&UnicodeChar::ReplacementCharacter), - ), - ( - "\\FFFFFF ", - get_unicode_char(&UnicodeChar::ReplacementCharacter), - ), - ( - "\\10FFFF ", - get_unicode_char(&UnicodeChar::ReplacementCharacter), - ), + ("\\000000 ", get_unicode_char(&UnicodeChar::ReplacementCharacter)), + ("\\FFFFFF ", get_unicode_char(&UnicodeChar::ReplacementCharacter)), + ("\\10FFFF ", get_unicode_char(&UnicodeChar::ReplacementCharacter)), ]; let mut tokenizer = Tokenizer::new(&mut chars, Location::default()); for (raw_escaped, escaped_char) in escaped_chars { - tokenizer - .stream - .read_from_str(raw_escaped, Some(Encoding::UTF8)); + tokenizer.stream.read_from_str(raw_escaped, Some(Encoding::UTF8)); assert_eq!(tokenizer.consume_escaped_token(), escaped_char); } } @@ -1062,30 +1036,16 @@ mod test { "url(https://gosub.io/)", Token::new_url("https://gosub.io/", Location::default()), ), - ( - "url( gosub.io )", - Token::new_url("gosub.io", Location::default()), - ), - ( - "url(gosub\u{002E}io)", - Token::new_url("gosub.io", Location::default()), - ), - ( - "url(gosub\u{FFFD}io)", - Token::new_url("gosub๏ฟฝio", Location::default()), - ), - ( - "url(gosub\u{0000}io)", - Token::new_bad_url("gosub", Location::default()), - ), + ("url( gosub.io )", Token::new_url("gosub.io", Location::default())), + ("url(gosub\u{002E}io)", Token::new_url("gosub.io", Location::default())), + ("url(gosub\u{FFFD}io)", Token::new_url("gosub๏ฟฝio", Location::default())), + ("url(gosub\u{0000}io)", Token::new_bad_url("gosub", Location::default())), ]; let mut tokenizer = Tokenizer::new(&mut chars, Location::default()); for (raw_url, url_token) in urls { - tokenizer - .stream - .read_from_str(raw_url, Some(Encoding::UTF8)); + tokenizer.stream.read_from_str(raw_url, Some(Encoding::UTF8)); assert_token_eq!(tokenizer.consume_ident_like_seq(), url_token); } } @@ -1101,47 +1061,24 @@ mod test { ("url( \'", Token::new_function("url", Location::default())), ("url(\"", Token::new_function("url", Location::default())), ("attr('", Token::new_function("attr", Location::default())), - ( - "rotateX( '", - Token::new_function("rotateX", Location::default()), - ), - ( - "rotateY( \"", - Token::new_function("rotateY", Location::default()), - ), + ("rotateX( '", Token::new_function("rotateX", Location::default())), + ("rotateY( \"", Token::new_function("rotateY", Location::default())), ("-rgba(", Token::new_function("-rgba", Location::default())), - ( - "--rgba(", - Token::new_function("--rgba", Location::default()), - ), - ( - "-\\26 -rgba(", - Token::new_function("-&-rgba", Location::default()), - ), + ("--rgba(", Token::new_function("--rgba", Location::default())), + ("-\\26 -rgba(", Token::new_function("-&-rgba", Location::default())), ("0rgba()", Token::new_function("0rgba", Location::default())), - ( - "-0rgba()", - Token::new_function("-0rgba", Location::default()), - ), + ("-0rgba()", Token::new_function("-0rgba", Location::default())), ("_rgba()", Token::new_function("_rgba", Location::default())), ("rgbรข()", Token::new_function("rgbรข", Location::default())), - ( - "\\30rgba()", - Token::new_function("0rgba", Location::default()), - ), + ("\\30rgba()", Token::new_function("0rgba", Location::default())), ("rgba ()", Token::new_ident("rgba", Location::default())), - ( - "-\\-rgba(", - Token::new_function("--rgba", Location::default()), - ), + ("-\\-rgba(", Token::new_function("--rgba", Location::default())), ]; let mut tokenizer = Tokenizer::new(&mut chars, Location::default()); for (raw_function, function_token) in functions { - tokenizer - .stream - .read_from_str(raw_function, Some(Encoding::UTF8)); + tokenizer.stream.read_from_str(raw_function, Some(Encoding::UTF8)); assert_token_eq!(tokenizer.consume_ident_like_seq(), function_token); } } @@ -1151,10 +1088,7 @@ mod test { let mut chars = ByteStream::new(Encoding::UTF8, None); let numeric_tokens = vec![ - ( - "1.1rem", - Token::new_dimension(1.1, "rem", Location::default()), - ), + ("1.1rem", Token::new_dimension(1.1, "rem", Location::default())), ("1px", Token::new_dimension(1.0, "px", Location::default())), ("1em", Token::new_dimension(1.0, "em", Location::default())), ("1 em", Token::new_number(1.0, Location::default())), @@ -1167,9 +1101,7 @@ mod test { let mut tokenizer = Tokenizer::new(&mut chars, Location::default()); for (raw_token, token) in numeric_tokens { - tokenizer - .stream - .read_from_str(raw_token, Some(Encoding::UTF8)); + tokenizer.stream.read_from_str(raw_token, Some(Encoding::UTF8)); assert_token_eq!(tokenizer.consume_numeric_token(), token); } } @@ -1179,10 +1111,7 @@ mod test { let mut stream = ByteStream::new(Encoding::UTF8, None); let string_tokens = vec![ - ( - "'line\nnewline'", - Token::new_bad_string("line", Location::default()), - ), + ("'line\nnewline'", Token::new_bad_string("line", Location::default())), ( "\"double quotes\"", Token::new_quoted_string("double quotes", Location::default()), @@ -1191,22 +1120,14 @@ mod test { "\'single quotes\'", Token::new_quoted_string("single quotes", Location::default()), ), - ( - "#hash#", - Token::new_quoted_string("hash", Location::default()), - ), - ( - "\"eof", - Token::new_quoted_string("eof", Location::default()), - ), + ("#hash#", Token::new_quoted_string("hash", Location::default())), + ("\"eof", Token::new_quoted_string("eof", Location::default())), ("\"\"", Token::new_quoted_string("", Location::default())), ]; for (raw_string, string_token) in string_tokens { let mut tokenizer = Tokenizer::new(&mut stream, Location::default()); - tokenizer - .stream - .read_from_str(raw_string, Some(Encoding::UTF8)); + tokenizer.stream.read_from_str(raw_string, Some(Encoding::UTF8)); tokenizer.stream.close(); let t = tokenizer.consume_string_token(); @@ -1218,10 +1139,7 @@ mod test { fn produce_stream_of_double_quoted_strings() { let mut stream = ByteStream::new(Encoding::UTF8, None); - stream.read_from_str( - "\"\" \"Lorem 'รฎpsum'\" \"a\\\nb\" \"a\nb \"eof", - Some(Encoding::UTF8), - ); + stream.read_from_str("\"\" \"Lorem 'รฎpsum'\" \"a\\\nb\" \"a\nb \"eof", Some(Encoding::UTF8)); stream.close(); let tokens = vec![ @@ -1253,10 +1171,7 @@ mod test { fn procude_stream_of_single_quoted_strings() { let mut stream = ByteStream::new(Encoding::UTF8, None); - stream.read_from_str( - "'' 'Lorem \"รฎpsum\"' 'a\\\nb' 'a\nb 'eof", - Some(Encoding::UTF8), - ); + stream.read_from_str("'' 'Lorem \"รฎpsum\"' 'a\\\nb' 'a\nb 'eof", Some(Encoding::UTF8)); stream.close(); let tokens = vec![ @@ -1446,10 +1361,7 @@ mod test { fn parse_cdo_and_cdc() { let mut stream = ByteStream::new(Encoding::UTF8, None); - stream.read_from_str( - "/* CDO/CDC are not special */ {}", - Some(Encoding::UTF8), - ); + stream.read_from_str("/* CDO/CDC are not special */ {}", Some(Encoding::UTF8)); stream.close(); let tokens = vec![ @@ -1857,7 +1769,10 @@ mod test { fn parse_css_seq_3() { let mut stream = ByteStream::new(Encoding::UTF8, None); - stream.read_from_str("\\- red0 -red --red -\\-red\\ blue 0red -0red \\0000red _Red .red rรชd r\\รชd \\007F\\0080\\0081", Some(Encoding::UTF8)); + stream.read_from_str( + "\\- red0 -red --red -\\-red\\ blue 0red -0red \\0000red _Red .red rรชd r\\รชd \\007F\\0080\\0081", + Some(Encoding::UTF8), + ); stream.close(); let tokens = vec![ @@ -1954,10 +1869,7 @@ mod test { tokenizer.lookahead(1), Token::new(TokenType::RBracket, Location::default()) ); - assert_token_eq!( - tokenizer.lookahead(4), - Token::new(TokenType::Eof, Location::default()) - ); + assert_token_eq!(tokenizer.lookahead(4), Token::new(TokenType::Eof, Location::default())); assert_token_eq!( tokenizer.consume(), diff --git a/crates/gosub_css3/src/walker.rs b/crates/gosub_css3/src/walker.rs index e0e93d7c3..955020334 100644 --- a/crates/gosub_css3/src/walker.rs +++ b/crates/gosub_css3/src/walker.rs @@ -46,11 +46,7 @@ fn inner_walk(node: &Node, depth: usize, f: &mut dyn Write) -> Result<(), std::i // writeln!(f, "{} - block: ", prefix)?; inner_walk(block.as_ref().unwrap(), depth + 1, f)?; } - NodeType::AtRule { - name, - prelude, - block, - } => { + NodeType::AtRule { name, prelude, block } => { writeln!(f, "{}[AtRule] name: {}", prefix, name)?; if prelude.is_some() { inner_walk(prelude.as_ref().unwrap(), depth + 1, f)?; @@ -219,10 +215,7 @@ fn inner_walk(node: &Node, depth: usize, f: &mut dyn Write) -> Result<(), std::i writeln!(f, "{}[MSFunction]", prefix)?; inner_walk(func, depth + 1, f)?; } - NodeType::MSIdent { - value, - default_value, - } => { + NodeType::MSIdent { value, default_value } => { writeln!( f, "{}[MSIdent] value: {} default_value: {}", diff --git a/crates/gosub_styling/tools/generate_definitions/go.mod b/crates/gosub_css3/tools/generate_definitions/go.mod similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/go.mod rename to crates/gosub_css3/tools/generate_definitions/go.mod diff --git a/crates/gosub_styling/tools/generate_definitions/main.go b/crates/gosub_css3/tools/generate_definitions/main.go similarity index 98% rename from crates/gosub_styling/tools/generate_definitions/main.go rename to crates/gosub_css3/tools/generate_definitions/main.go index 584d4fcba..64fae373c 100644 --- a/crates/gosub_styling/tools/generate_definitions/main.go +++ b/crates/gosub_css3/tools/generate_definitions/main.go @@ -21,7 +21,7 @@ const ( const ( exportType = Both - ResourcePath = "crates/gosub_styling/resources/definitions" + ResourcePath = "crates/gosub_css3/resources/definitions" SingleFilePath = ResourcePath + "/definitions.json" MultiFileDir = ResourcePath MultiFilePrefix = "definitions_" diff --git a/crates/gosub_styling/tools/generate_definitions/mdn/mdn.go b/crates/gosub_css3/tools/generate_definitions/mdn/mdn.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/mdn/mdn.go rename to crates/gosub_css3/tools/generate_definitions/mdn/mdn.go diff --git a/crates/gosub_styling/tools/generate_definitions/patch/patch_darwin.go b/crates/gosub_css3/tools/generate_definitions/patch/patch_darwin.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/patch/patch_darwin.go rename to crates/gosub_css3/tools/generate_definitions/patch/patch_darwin.go diff --git a/crates/gosub_styling/tools/generate_definitions/patch/patch_linux.go b/crates/gosub_css3/tools/generate_definitions/patch/patch_linux.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/patch/patch_linux.go rename to crates/gosub_css3/tools/generate_definitions/patch/patch_linux.go diff --git a/crates/gosub_styling/tools/generate_definitions/patch/patch_windows.go b/crates/gosub_css3/tools/generate_definitions/patch/patch_windows.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/patch/patch_windows.go rename to crates/gosub_css3/tools/generate_definitions/patch/patch_windows.go diff --git a/crates/gosub_styling/tools/generate_definitions/patch/patcher.go b/crates/gosub_css3/tools/generate_definitions/patch/patcher.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/patch/patcher.go rename to crates/gosub_css3/tools/generate_definitions/patch/patcher.go diff --git a/crates/gosub_styling/tools/generate_definitions/specs/specs.go b/crates/gosub_css3/tools/generate_definitions/specs/specs.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/specs/specs.go rename to crates/gosub_css3/tools/generate_definitions/specs/specs.go diff --git a/crates/gosub_styling/tools/generate_definitions/utils/stringmaybearray.go b/crates/gosub_css3/tools/generate_definitions/utils/stringmaybearray.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/utils/stringmaybearray.go rename to crates/gosub_css3/tools/generate_definitions/utils/stringmaybearray.go diff --git a/crates/gosub_styling/tools/generate_definitions/utils/types.go b/crates/gosub_css3/tools/generate_definitions/utils/types.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/utils/types.go rename to crates/gosub_css3/tools/generate_definitions/utils/types.go diff --git a/crates/gosub_styling/tools/generate_definitions/utils/utils.go b/crates/gosub_css3/tools/generate_definitions/utils/utils.go similarity index 89% rename from crates/gosub_styling/tools/generate_definitions/utils/utils.go rename to crates/gosub_css3/tools/generate_definitions/utils/utils.go index 3599e0866..fd2a06441 100644 --- a/crates/gosub_styling/tools/generate_definitions/utils/utils.go +++ b/crates/gosub_css3/tools/generate_definitions/utils/utils.go @@ -10,9 +10,9 @@ const ( REPO = "w3c/webref" LOCATION = "ed/css" PATCH_LOCATION = "ed/csspatches" - CACHE_DIR = "crates/gosub_styling/resources/cache" + CACHE_DIR = "crates/gosub_css3/resources/cache" CACHE_INDEX_FILE = CACHE_DIR + "/index/cache_index.json" - CUSTOM_PATCH_DIR = "crates/gosub_styling/resources/patches" + CUSTOM_PATCH_DIR = "crates/gosub_css3/resources/patches" BRANCH = "curated" ) diff --git a/crates/gosub_styling/tools/generate_definitions/webref/webref.go b/crates/gosub_css3/tools/generate_definitions/webref/webref.go similarity index 100% rename from crates/gosub_styling/tools/generate_definitions/webref/webref.go rename to crates/gosub_css3/tools/generate_definitions/webref/webref.go diff --git a/crates/gosub_html5/Cargo.toml b/crates/gosub_html5/Cargo.toml index 25ec4762f..ce51b8e9c 100644 --- a/crates/gosub_html5/Cargo.toml +++ b/crates/gosub_html5/Cargo.toml @@ -15,8 +15,6 @@ thiserror = "1.0.64" url = { version = "2.5.2", features = [] } log = { version = "0.4.22", features = [] } - - [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ureq = "2.10.1" diff --git a/crates/gosub_html5/benches/tree_construction.rs b/crates/gosub_html5/benches/tree_construction.rs index 077cf0aae..b118414d1 100644 --- a/crates/gosub_html5/benches/tree_construction.rs +++ b/crates/gosub_html5/benches/tree_construction.rs @@ -1,4 +1,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; +use gosub_css3::system::Css3System; +use gosub_html5::document::document_impl::DocumentImpl; +use gosub_html5::parser::Html5Parser; use gosub_testing::testing::tree_construction; use gosub_testing::testing::tree_construction::Harness; @@ -8,8 +11,7 @@ fn criterion_benchmark(c: &mut Criterion) { // Careful about reading files inside the closure let filenames = Some(&["tests1.dat"][..]); - let fixtures = - tree_construction::fixture::read_fixtures(filenames).expect("problem loading fixtures"); + let fixtures = tree_construction::fixture::read_fixtures(filenames).expect("problem loading fixtures"); let mut harness = Harness::new(); @@ -18,7 +20,10 @@ fn criterion_benchmark(c: &mut Criterion) { for root in &fixtures { for test in &root.tests { for &scripting_enabled in test.script_modes() { - let _ = harness.run_test(test.clone(), scripting_enabled); + let _ = harness.run_test::, Css3System>, Css3System>( + test.clone(), + scripting_enabled, + ); } } } diff --git a/crates/gosub_html5/docs/parsing.md b/crates/gosub_html5/docs/parsing.md index 04dc7631d..2eec449f6 100644 --- a/crates/gosub_html5/docs/parsing.md +++ b/crates/gosub_html5/docs/parsing.md @@ -16,14 +16,14 @@ Next, we need to create a document, which will be the main object that will be f data that is generated during the parsing of the HTML. This also includes any stylesheets that are found, both internally and externally. ```rust - let document = DocumentBuilder::new_document(); + let doc_handle = DocumentBuilderImpl::new_document(); ``` -Note that a document itself isn't a document, but a HANDLE to a document (a `DocumentHandle`). Once we have our document handle, we can start the parser +Note that a doc_handle itself isn't a document, but a HANDLE to a document (a `DocumentHandle`). Once we have our document handle, we can start the parser by calling the `parse_document` method on the `Html5Parser` struct. This method will return a list of parse errors, if any. ```rust - let parse_errors = Html5Parser::parse_document(&mut stream, Document::clone(&document), None)?; + let parse_errors = Html5Parser::parse_document(&mut stream, doc_handle.clone(), None)?; for e in parse_errors { println!("Parse Error: {}", e.message); diff --git a/crates/gosub_html5/src/document.rs b/crates/gosub_html5/src/document.rs new file mode 100644 index 000000000..6456d6b49 --- /dev/null +++ b/crates/gosub_html5/src/document.rs @@ -0,0 +1,5 @@ +pub mod builder; +pub mod document_impl; +pub mod fragment; +pub mod query; +pub mod task_queue; diff --git a/crates/gosub_html5/src/document/builder.rs b/crates/gosub_html5/src/document/builder.rs new file mode 100644 index 000000000..2cdd48dc3 --- /dev/null +++ b/crates/gosub_html5/src/document/builder.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; + +use gosub_shared::traits::css3::CssSystem; +use url::Url; + +use crate::document::document_impl::DocumentImpl; +use crate::node::HTML_NAMESPACE; +use crate::DocumentHandle; +use gosub_shared::traits::document::DocumentBuilder; +use gosub_shared::traits::document::{Document, DocumentType}; +use gosub_shared::traits::node::{Node, QuirksMode}; + +/// This struct will be used to create a fully initialized document or document fragment +pub struct DocumentBuilderImpl {} + +impl DocumentBuilder for DocumentBuilderImpl { + type Document = DocumentImpl; + + /// Creates a new document with a document root node + fn new_document(url: Option) -> DocumentHandle { + >::new(DocumentType::HTML, url, None) + } + + /// Creates a new document fragment with the context as the root node + fn new_document_fragment( + context_node: &>::Node, + quirks_mode: QuirksMode, + ) -> DocumentHandle { + let handle = context_node.handle(); + + // Create a new document with an HTML node as the root node + let fragment_root_node = >::new_element_node( + handle.clone(), + "html", + Some(HTML_NAMESPACE), + HashMap::new(), + context_node.location(), + ); + let mut fragment_handle = + >::new(DocumentType::HTML, None, Some(fragment_root_node)); + + // let context_node = context_node.clone(); + match quirks_mode { + QuirksMode::Quirks => { + fragment_handle.get_mut().set_quirks_mode(QuirksMode::Quirks); + } + QuirksMode::LimitedQuirks => { + fragment_handle.get_mut().set_quirks_mode(QuirksMode::LimitedQuirks); + } + _ => {} + } + + fragment_handle + } +} diff --git a/crates/gosub_html5/src/document/document_impl.rs b/crates/gosub_html5/src/document/document_impl.rs new file mode 100755 index 000000000..374677a9f --- /dev/null +++ b/crates/gosub_html5/src/document/document_impl.rs @@ -0,0 +1,2277 @@ +use crate::DocumentHandle; +use core::fmt::Debug; +use gosub_shared::traits::document::{Document as OtherDocument, Document, DocumentType}; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::rc::Rc; +use url::Url; + +use crate::document::builder::DocumentBuilderImpl; +use crate::document::fragment::DocumentFragmentImpl; +use crate::document::task_queue::is_valid_id_attribute_value; +use crate::node::arena::NodeArena; +use crate::node::data::comment::CommentData; +use crate::node::data::doctype::DocTypeData; +use crate::node::data::document::DocumentData; +use crate::node::data::element::{ClassListImpl, ElementData}; +use crate::node::data::text::TextData; +use crate::node::node_impl::{NodeDataTypeInternal, NodeImpl}; +use crate::node::visitor::Visitor; +use gosub_shared::byte_stream::Location; +use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::node::Node; +use gosub_shared::traits::node::QuirksMode; + +/// Defines a document +#[derive(Debug)] +pub struct DocumentImpl { + // pub handle: Weak>, + /// URL of the given document (if any) + pub url: Option, + /// Holds and owns all nodes in the document + pub(crate) arena: NodeArena, C>, + /// HTML elements with ID (e.g.,

) + named_id_elements: HashMap, + /// Document type of this document + pub doctype: DocumentType, + /// Quirks mode of this document + pub quirks_mode: QuirksMode, + /// Loaded stylesheets as extracted from the document + pub stylesheets: Vec, +} + +impl PartialEq for DocumentImpl { + fn eq(&self, other: &Self) -> bool { + self.url == other.url + && self.arena == other.arena + && self.named_id_elements == other.named_id_elements + && self.doctype == other.doctype + && self.quirks_mode == other.quirks_mode + && self.stylesheets == other.stylesheets + } +} + +impl Document for DocumentImpl { + type Node = NodeImpl; + type Fragment = DocumentFragmentImpl; + type Builder = DocumentBuilderImpl; + + /// Creates a new document without a doc handle + #[must_use] + fn new(document_type: DocumentType, url: Option, root_node: Option) -> DocumentHandle { + let doc = Self { + url, + arena: NodeArena::new(), + named_id_elements: HashMap::new(), + doctype: document_type, + quirks_mode: QuirksMode::NoQuirks, + stylesheets: Vec::new(), + }; + + let mut doc_handle = DocumentHandle(Rc::new(RefCell::new(doc)), Default::default()); + + if let Some(node) = root_node { + doc_handle.get_mut().register_node(node); + } else { + let node = Self::Node::new_document(doc_handle.clone(), Location::default(), QuirksMode::NoQuirks); + doc_handle.get_mut().arena.register_node(node); + } + + doc_handle + } + + /// Returns the URL of the document, or "" when no location is set + fn url(&self) -> Option { + self.url.clone() + } + + fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) { + self.quirks_mode = quirks_mode; + } + + fn quirks_mode(&self) -> QuirksMode { + self.quirks_mode + } + + fn set_doctype(&mut self, doctype: DocumentType) { + self.doctype = doctype; + } + + fn doctype(&self) -> DocumentType { + self.doctype + } + + /// Fetches a node by id or returns None when no node with this ID is found + fn node_by_id(&self, node_id: NodeId) -> Option<&Self::Node> { + self.arena.node_ref(node_id) + } + + fn node_by_named_id(&self, id: &str) -> Option<&Self::Node> { + self.named_id_elements + .get(id) + .and_then(|node_id| self.arena.node_ref(*node_id)) + } + + fn stylesheets(&self) -> &Vec { + &self.stylesheets + } + + // /// Add given node to the named ID elements + // fn add_named_id(&mut self, id: &str, node_id: NodeId) { + // self.named_id_elements.insert(id.to_string(), node_id); + // } + // + // /// Remove a named ID from the document + // fn remove_named_id(&mut self, id: &str) { + // self.named_id_elements.remove(id); + // } + + fn add_stylesheet(&mut self, stylesheet: C::Stylesheet) { + self.stylesheets.push(stylesheet); + } + + /// returns the root node + fn get_root(&self) -> &Self::Node { + self.arena.node_ref(NodeId::root()).expect("Root node not found !?") + } + + fn attach_node(&mut self, node_id: NodeId, parent_id: NodeId, position: Option) { + // Check if any children of node have parent as child. This will keep adding the node to itself + if parent_id == node_id || self.has_node_id_recursive(node_id, parent_id) { + return; + } + + if let Some(parent_node) = self.node_by_id(parent_id) { + let mut parent_node = parent_node.clone(); + + // Make sure position can never be larger than the number of children in the parent + match position { + Some(position) => { + if position > parent_node.children().len() { + parent_node.push(node_id); + } else { + parent_node.insert(node_id, position); + } + } + None => { + // No position given, add to end of the children list + parent_node.push(node_id); + } + } + + self.update_node(parent_node); + } + + let mut node = self.arena.node(node_id).unwrap(); + node.parent = Some(parent_id); + self.update_node(node); + } + + // /// returns the root node + // fn get_root_mut(&mut self) -> &mut Self::Node { + // self.arena.node_mut(NodeId::root()).expect("Root node not found !?") + // } + + fn detach_node(&mut self, node_id: NodeId) { + let parent = self.node_by_id(node_id).expect("node not found").parent_id(); + + if let Some(parent_id) = parent { + let mut parent_node = self.node_by_id(parent_id).expect("parent node not found").clone(); + parent_node.remove(node_id); + self.update_node(parent_node); + + let mut node = self.node_by_id(node_id).expect("node not found").clone(); + node.set_parent(None); + self.update_node(node); + } + } + + /// Relocates a node to another parent node + fn relocate_node(&mut self, node_id: NodeId, parent_id: NodeId) { + let node = self.arena.node_ref(node_id).unwrap(); + assert!(node.registered, "Node is not registered to the arena"); + + if node.parent.is_some() && node.parent.unwrap() == parent_id { + // Nothing to do when we want to relocate to its own parent + return; + } + + self.detach_node(node_id); + self.attach_node(node_id, parent_id, None); + } + + fn update_node(&mut self, node: Self::Node) { + if !node.is_registered() { + log::warn!("Node is not registered to the arena"); + return; + } + + self.on_document_node_mutation(&node); + self.arena.update_node(node); + } + + fn update_node_ref(&mut self, node: &Self::Node) { + if !node.is_registered() { + log::warn!("Node is not registered to the arena"); + return; + } + + self.on_document_node_mutation(node); + self.arena.update_node(node.clone()); + } + + /// Removes a node by id from the arena. Note that this does not check the nodelist itself to see + /// if the node is still available as a child or parent in the tree. + fn delete_node_by_id(&mut self, node_id: NodeId) { + let node = self.arena.node(node_id).unwrap(); + let parent_id = node.parent_id(); + + if let Some(parent_id) = parent_id { + let mut parent = self.node_by_id(parent_id).unwrap().clone(); + parent.remove(node_id); + self.update_node(parent); + } + + self.arena.delete_node(node_id); + } + + // /// Returns the parent node of the given node, or None when no parent is found + // fn parent_node(&self, node: &Self::Node) -> Option<&Self::Node> { + // match node.parent_id() { + // Some(parent_node_id) => self.node_by_id(parent_node_id), + // None => None, + // } + // } + + /// Retrieves the next sibling NodeId (to the right) of the reference_node or None. + fn get_next_sibling(&self, reference_node: NodeId) -> Option { + let node = self.node_by_id(reference_node)?; + let parent = self.node_by_id(node.parent_id()?)?; + + let idx = parent + .children() + .iter() + .position(|&child_id| child_id == reference_node)?; + + let next_idx = idx + 1; + if parent.children().len() > next_idx { + return Some(parent.children()[next_idx]); + } + + None + } + + fn node_count(&self) -> usize { + self.arena.node_count() + } + + fn peek_next_id(&self) -> NodeId { + self.arena.peek_next_id() + } + + /// Register a node. It is not connected to anything yet, but it does receive a nodeId + fn register_node(&mut self, mut node: Self::Node) -> NodeId { + let node_id = self.arena.get_next_id(); + + node.set_id(node_id); + + if node.is_element_node() { + let element_data = node.get_element_data_mut().unwrap(); + element_data.node_id = Some(node_id); + } + + self.on_document_node_mutation(&node); + + self.arena.register_node_with_node_id(node, node_id); + + node_id + } + + /// Inserts a node to the parent node at the given position in the children (or none + /// to add at the end). Will automatically register the node if not done so already + fn register_node_at(&mut self, node: Self::Node, parent_id: NodeId, position: Option) -> NodeId { + self.on_document_node_mutation(&node); + + let node_id = self.register_node(node); + self.attach_node(node_id, parent_id, position); + + node_id + } + + /// Creates a new document node + fn new_document_node(handle: DocumentHandle, quirks_mode: QuirksMode, location: Location) -> Self::Node { + NodeImpl::new( + handle.clone(), + location, + &NodeDataTypeInternal::Document(DocumentData::new(quirks_mode)), + ) + } + + fn new_doctype_node( + handle: DocumentHandle, + name: &str, + public_id: Option<&str>, + system_id: Option<&str>, + location: Location, + ) -> Self::Node { + NodeImpl::new( + handle.clone(), + location, + &NodeDataTypeInternal::DocType(DocTypeData::new(name, public_id.unwrap_or(""), system_id.unwrap_or(""))), + ) + } + + /// Creates a new comment node + fn new_comment_node(handle: DocumentHandle, comment: &str, location: Location) -> Self::Node { + NodeImpl::new( + handle.clone(), + location, + &NodeDataTypeInternal::Comment(CommentData::with_value(comment)), + ) + } + + /// Creates a new text node + fn new_text_node(handle: DocumentHandle, value: &str, location: Location) -> Self::Node { + NodeImpl::new( + handle.clone(), + location, + &NodeDataTypeInternal::Text(TextData::with_value(value)), + ) + } + + /// Creates a new element node + fn new_element_node( + handle: DocumentHandle, + name: &str, + namespace: Option<&str>, + attributes: HashMap, + location: Location, + ) -> Self::Node { + // Extract class list from the class-attribute (if exists) + let class_list = match attributes.get("class") { + Some(class_value) => ClassListImpl::from(class_value.as_str()), + None => ClassListImpl::default(), + }; + + NodeImpl::new( + handle.clone(), + location, + &NodeDataTypeInternal::Element(ElementData::new( + handle.clone(), + name, + namespace, + attributes, + class_list, + )), + ) + } + + fn write(&self) -> String { + self.write_from_node(NodeId::root()) + } + + fn write_from_node(&self, _node_id: NodeId) -> String { + todo!(); //This should definitely be implemented + } + + fn cloned_node_by_id(&self, node_id: NodeId) -> Option { + self.arena.node(node_id) + } +} + +impl DocumentImpl { + // Called whenever a node is being mutated in the document. + fn on_document_node_mutation(&mut self, node: &NodeImpl) { + // self.on_document_node_mutation_update_id_in_node(node); + self.on_document_node_mutation_update_named_id(node); + } + + /// Update document's named id structure when the node has ID elements + fn on_document_node_mutation_update_named_id(&mut self, node: &NodeImpl) { + if !node.is_element_node() { + return; + } + + let element_data = node.get_element_data().unwrap(); + if let Some(id_value) = element_data.attributes.get("id") { + // When we have an ID attribute: update the named ID element map. + if is_valid_id_attribute_value(id_value) { + match self.named_id_elements.entry(id_value.clone()) { + Entry::Vacant(entry) => { + entry.insert(node.id()); + } + Entry::Occupied(_) => {} + } + } + } else { + // If we don't have an ID attribute in the node, make sure that we remove and "old" id's that might be in the map. + self.named_id_elements.retain(|_, id| *id != node.id()); + } + } + + /// Print a node and all its children in a tree-like structure + pub fn print_tree( + &self, + node: & as Document>::Node, + prefix: String, + last: bool, + f: &mut Formatter, + ) { + let mut buffer = prefix.clone(); + if last { + buffer.push_str("โ””โ”€ "); + } else { + buffer.push_str("โ”œโ”€ "); + } + // buffer.push_str(format!("{} ", node.id).as_str()); + + match &node.data { + NodeDataTypeInternal::Document(_) => { + _ = writeln!(f, "{buffer}Document"); + } + NodeDataTypeInternal::DocType(DocTypeData { + name, + pub_identifier, + sys_identifier, + }) => { + _ = writeln!(f, r#"{buffer}"#,); + } + NodeDataTypeInternal::Text(TextData { value, .. }) => { + _ = writeln!(f, r#"{buffer}"{value}""#); + } + NodeDataTypeInternal::Comment(CommentData { value, .. }) => { + _ = writeln!(f, "{buffer}"); + } + NodeDataTypeInternal::Element(element) => { + _ = write!(f, "{}<{}", buffer, element.name); + for (key, value) in &element.attributes { + _ = write!(f, " {key}={value}"); + } + + // for (key, value) in node.style.borrow().iter() { + // _ = write!(f, "[CSS: {key}={value}]"); + // } + + _ = writeln!(f, ">"); + } + } + + if prefix.len() > 40 { + _ = writeln!(f, "..."); + return; + } + + let mut buffer = prefix; + if last { + buffer.push_str(" "); + } else { + buffer.push_str("โ”‚ "); + } + + let len = node.children.len(); + for (i, child_id) in node.children.iter().enumerate() { + let child_node = self.node_by_id(*child_id).expect("Child not found"); + self.print_tree(child_node, buffer.clone(), i == len - 1, f); + } + } +} + +impl Display for DocumentImpl { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let root = self.get_root(); + self.print_tree(root, "".to_string(), true, f); + Ok(()) + } +} + +impl DocumentImpl { + /// Fetches a node by named id (string) or returns None when no node with this ID is found + pub fn get_node_by_named_id(&self, named_id: &str) -> Option<& as Document>::Node> { + let node_id = self.named_id_elements.get(named_id)?; + self.arena.node_ref(*node_id) + } + + // /// Fetches a mutable node by named id (string) or returns None when no node with this ID is found + // pub fn get_node_by_named_id_mut( + // &mut self, + // named_id: &str, + // ) -> Option<&mut as Document>::Node> { + // let node_id = self.named_id_elements.get(named_id)?; + // self.arena.node_mut(*node_id) + // } + + // pub fn count_nodes(&self) -> usize { + // self.arena.count_nodes() + // } + + pub fn has_node_id_recursive(&self, parent_id: NodeId, target_node_id: NodeId) -> bool { + let parent = self.arena.node_ref(parent_id); + if parent.is_none() { + return false; + } + + for child_node_id in parent.unwrap().children() { + if *child_node_id == target_node_id { + return true; + } + if self.has_node_id_recursive(*child_node_id, target_node_id) { + return true; + } + } + + false + } + + pub fn peek_next_id(&self) -> NodeId { + self.arena.peek_next_id() + } + + pub fn nodes(&self) -> &HashMap as Document>::Node> { + self.arena.nodes() + } +} + +// Walk the document tree with the given visitor +pub fn walk_document_tree( + handle: DocumentHandle, C>, + visitor: &mut Box as Document>::Node, C>>, +) { + let binding = handle.get(); + let root = binding.get_root(); + internal_visit(handle.clone(), root, visitor); +} + +fn internal_visit( + handle: DocumentHandle, C>, + node: & as Document>::Node, + visitor: &mut Box as Document>::Node, C>>, +) { + visitor.document_enter(node); + + let binding = handle.get(); + for child_id in node.children() { + let child = binding.node_by_id(*child_id).unwrap(); + internal_visit(handle.clone(), child, visitor); + } + drop(binding); + + // Leave node + visitor.document_leave(node); +} + +/// Constructs an iterator from a given DocumentHandle. +/// WARNING: mutations in the document would be reflected +/// in the iterator. It's advised to consume the entire iterator +/// before mutating the document again. +pub struct TreeIterator, C: CssSystem> { + current_node_id: Option, + node_stack: Vec, + document: DocumentHandle, +} + +impl, C: CssSystem> TreeIterator { + #[must_use] + pub fn new(doc: DocumentHandle) -> Self { + let node_stack = vec![doc.get().get_root().id()]; + + Self { + current_node_id: None, + document: doc, + node_stack, + } + } +} + +impl, C: CssSystem> Iterator for TreeIterator { + type Item = NodeId; + + fn next(&mut self) -> Option { + self.current_node_id = self.node_stack.pop(); + + if let Some(current_node_id) = self.current_node_id { + let doc_read = self.document.get(); + + if let Some(sibling_id) = self.document.get().get_next_sibling(current_node_id) { + self.node_stack.push(sibling_id); + } + + if let Some(current_node) = doc_read.node_by_id(current_node_id) { + if let Some(&child_id) = current_node.children().first() { + self.node_stack.push(child_id); + } + } + } + + self.current_node_id + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::document::builder::DocumentBuilderImpl; + use crate::document::query::DocumentQuery; + use crate::document::task_queue::DocumentTaskQueue; + use crate::node::HTML_NAMESPACE; + use crate::parser::query::Query; + use crate::parser::tree_builder::TreeBuilder; + use gosub_css3::system::Css3System; + use gosub_shared::byte_stream::Location; + use gosub_shared::traits::document::DocumentBuilder; + use gosub_shared::traits::node::ClassList; + use gosub_shared::traits::node::ElementDataType; + use gosub_shared::traits::node::NodeType; + use std::collections::HashMap; + + type Document = DocumentImpl; + + #[test] + fn relocate() { + let mut doc_handle = DocumentBuilderImpl::new_document(None); + + let parent_node = Document::new_element_node( + doc_handle.clone(), + "parent", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let node1 = Document::new_element_node( + doc_handle.clone(), + "div1", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let node2 = Document::new_element_node( + doc_handle.clone(), + "div2", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let node3 = Document::new_element_node( + doc_handle.clone(), + "div3", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let node3_1 = Document::new_element_node( + doc_handle.clone(), + "div3_1", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + + let parent_id = doc_handle.get_mut().register_node_at(parent_node, NodeId::root(), None); + let node1_id = doc_handle.get_mut().register_node_at(node1, parent_id, None); + let node2_id = doc_handle.get_mut().register_node_at(node2, parent_id, None); + let node3_id = doc_handle.get_mut().register_node_at(node3, parent_id, None); + let node3_1_id = doc_handle.get_mut().register_node_at(node3_1, node3_id, None); + + assert_eq!( + format!("{}", doc_handle.get()), + r#"โ””โ”€ Document + โ””โ”€ + โ”œโ”€ + โ”œโ”€ + โ””โ”€ + โ””โ”€ +"# + ); + + doc_handle.get_mut().relocate_node(node3_1_id, node1_id); + assert_eq!( + format!("{}", doc_handle.get()), + r#"โ””โ”€ Document + โ””โ”€ + โ”œโ”€ + โ”‚ โ””โ”€ + โ”œโ”€ + โ””โ”€ +"# + ); + + doc_handle.get_mut().relocate_node(node1_id, node2_id); + assert_eq!( + format!("{}", doc_handle.get()), + r#"โ””โ”€ Document + โ””โ”€ + โ”œโ”€ + โ”‚ โ””โ”€ + โ”‚ โ””โ”€ + โ””โ”€ +"# + ); + } + + #[test] + fn verify_node_ids_in_element_data() { + let mut doc_handle = DocumentBuilderImpl::new_document(None); + + let node_1: NodeImpl = DocumentImpl::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let node_2: NodeImpl = DocumentImpl::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + + doc_handle.get_mut().register_node_at(node_1, NodeId::root(), None); + doc_handle.get_mut().register_node_at(node_2, NodeId::root(), None); + + let binding = doc_handle.get(); + let get_node1 = binding.node_by_id(NodeId::from(1usize)).unwrap(); + let get_node2 = binding.node_by_id(NodeId::from(2usize)).unwrap(); + + let NodeDataTypeInternal::Element(_) = &get_node1.data else { + panic!() + }; + assert_eq!(get_node1.id(), NodeId::from(1usize)); + + let NodeDataTypeInternal::Element(_) = &get_node2.data else { + panic!() + }; + assert_eq!(get_node2.id(), NodeId::from(2usize)); + } + + #[test] + fn document_task_queue() { + let doc_handle: DocumentHandle, Css3System> = DocumentBuilderImpl::new_document(None); + + // Using task queue to create the following structure initially: + //
+ //

+ // + // hey + //

+ // + //
+ + // then flush the queue and use it again to add an attribute to

: + //

hey

+ let mut task_queue = DocumentTaskQueue::new(doc_handle.clone()); + + // NOTE: only elements return the ID + let div_id = task_queue.create_element("div", NodeId::root(), None, HTML_NAMESPACE, Location::default()); + assert_eq!(div_id, NodeId::from(1usize)); + + let p_id = task_queue.create_element("p", div_id, None, HTML_NAMESPACE, Location::default()); + assert_eq!(p_id, NodeId::from(2usize)); + + task_queue.create_comment("comment inside p", p_id, Location::default()); + task_queue.create_text("hey", p_id, Location::default()); + task_queue.create_comment("comment inside div", div_id, Location::default()); + + // at this point, the DOM should have NO nodes (besides root) + assert_eq!(doc_handle.get().node_count(), 1); + + // validate our queue is loaded + assert!(!task_queue.is_empty()); + let errors = task_queue.flush(); + assert!(errors.is_empty()); + + // validate queue is empty + assert!(task_queue.is_empty()); + + // DOM should now have all our nodes + assert_eq!(doc_handle.get().arena.node_count(), 6); + + // NOTE: these checks are scoped separately since this is using an + // immutable borrow, and we make a mutable borrow after (to insert the attribute). + // We need this immutable borrow to die off before making a new mutable borrow + // (and again an immutable borrow for validation afterward) + { + // validate DOM is correctly laid out + let doc_read = doc_handle.get(); + let root = doc_read.get_root(); // + let root_children = &root.children; + + // div child + let div_child = doc_read.node_by_id(root_children[0]).unwrap(); + assert_eq!(div_child.type_of(), NodeType::ElementNode); + assert_eq!(div_child.get_element_data().unwrap().name, "div"); + let div_children = &div_child.children; + + // p child + let p_child = doc_read.node_by_id(div_children[0]).unwrap(); + assert_eq!(p_child.type_of(), NodeType::ElementNode); + assert_eq!(p_child.get_element_data().unwrap().name, "p"); + let p_children = &p_child.children; + + // comment inside p + let p_comment = doc_read.node_by_id(p_children[0]).unwrap(); + assert_eq!(p_comment.type_of(), NodeType::CommentNode); + let NodeDataTypeInternal::Comment(p_comment_data) = &p_comment.data else { + panic!() + }; + assert_eq!(p_comment_data.value, "comment inside p"); + + // body inside p + let p_body = doc_read.node_by_id(p_children[1]).unwrap(); + assert_eq!(p_body.type_of(), NodeType::TextNode); + let NodeDataTypeInternal::Text(p_body_data) = &p_body.data else { + panic!() + }; + assert_eq!(p_body_data.value, "hey"); + + // comment inside div + let div_comment = doc_read.node_by_id(div_children[1]).unwrap(); + assert_eq!(div_comment.type_of(), NodeType::CommentNode); + let NodeDataTypeInternal::Comment(div_comment_data) = &div_comment.data else { + panic!() + }; + assert_eq!(div_comment_data.value, "comment inside div"); + } + + // use task queue again to add an ID attribute + // NOTE: inserting attribute in task queue always succeeds + // since it doesn't touch DOM until flush + let _ = task_queue.insert_attribute("id", "myid", p_id, Location::default()); + let errors = task_queue.flush(); + println!("{:?}", errors); + assert!(errors.is_empty()); + + let doc_read = doc_handle.get(); + // validate ID is searchable in dom + assert_eq!(*doc_read.named_id_elements.get("myid").unwrap(), p_id); + + // validate attribute is applied to underlying element + let p_node = doc_read.node_by_id(p_id).unwrap(); + let NodeDataTypeInternal::Element(p_element) = &p_node.data else { + panic!() + }; + assert_eq!(p_element.attributes().get("id").unwrap(), "myid"); + } + + #[test] + fn task_queue_insert_attribute_failues() { + let doc_handle: DocumentHandle, Css3System> = + >::new_document(None); + + let mut task_queue = DocumentTaskQueue::new(doc_handle.clone()); + let div_id = task_queue.create_element("div", NodeId::root(), None, HTML_NAMESPACE, Location::default()); + task_queue.create_comment("content", div_id, Location::default()); // this is NodeId::from(2) + task_queue.flush(); + + // NOTE: inserting attribute in task queue always succeeds + // since it doesn't touch DOM until flush + let _ = task_queue.insert_attribute("id", "myid", NodeId::from(1usize), Location::default()); + let _ = task_queue.insert_attribute("id", "myid", NodeId::from(2usize), Location::default()); + let _ = task_queue.insert_attribute("id", "otherid", NodeId::from(2usize), Location::default()); + let _ = task_queue.insert_attribute("id", "dummyid", NodeId::from(42usize), Location::default()); + let _ = task_queue.insert_attribute("id", "my id", NodeId::from(1usize), Location::default()); + let _ = task_queue.insert_attribute("id", "123", NodeId::from(1usize), Location::default()); + let _ = task_queue.insert_attribute("id", "", NodeId::from(1usize), Location::default()); + let errors = task_queue.flush(); + for error in &errors { + println!("{}", error); + } + assert_eq!(errors.len(), 5); + assert_eq!(errors[0], "ID attribute value 'myid' already exists in DOM"); + assert_eq!(errors[1], "Node id 2 is not an element"); + assert_eq!(errors[2], "Node id 42 not found"); + assert_eq!(errors[3], "ID attribute value 'my id' did not pass validation"); + assert_eq!(errors[4], "ID attribute value '' did not pass validation"); + + // validate that invalid changes did not apply to DOM + let doc_read = doc_handle.get(); + assert!(!doc_read.named_id_elements.contains_key("my id")); + assert!(!doc_read.named_id_elements.contains_key("")); + } + + // this is basically a replica of document_task_queue() test + // but using tree builder directly instead of the task queue + #[test] + fn document_tree_builder() { + let mut doc_handle: DocumentHandle, Css3System> = + >::new_document(None); + + // Using tree builder to create the following structure: + //
+ //

+ // + // hey + //

+ // + //
+ + // NOTE: only elements return the ID + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + assert_eq!(div_id, NodeId::from(1usize)); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id, None); + assert_eq!(p_id, NodeId::from(2usize)); + + let node = Document::new_comment_node(doc_handle.clone(), "comment inside p", Location::default()); + doc_handle.get_mut().register_node_at(node, p_id, None); + + let node = Document::new_text_node(doc_handle.clone(), "hey", Location::default()); + doc_handle.get_mut().register_node_at(node, p_id, None); + + let node = Document::new_comment_node(doc_handle.clone(), "comment inside div", Location::default()); + doc_handle.get_mut().register_node_at(node, div_id, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("id", "myid"); + } + binding.update_node(node); + // binding.add_named_id("myid", p_id); + drop(binding); + + // DOM should now have all our nodes + assert_eq!(doc_handle.get().node_count(), 6); + + // validate DOM is correctly laid out + let doc_read = doc_handle.get(); + let root = doc_read.get_root(); // + let root_children = &root.children; + + // div child + let div_child = doc_read.node_by_id(root_children[0]).unwrap(); + assert_eq!(div_child.type_of(), NodeType::ElementNode); + assert_eq!(div_child.get_element_data().unwrap().name, "div"); + let div_children = &div_child.children; + + // p child + let p_child = doc_read.node_by_id(div_children[0]).unwrap(); + assert_eq!(p_child.type_of(), NodeType::ElementNode); + assert_eq!(p_child.get_element_data().unwrap().name, "p"); + let p_children = &p_child.children; + + // comment inside p + let p_comment = doc_read.node_by_id(p_children[0]).unwrap(); + assert_eq!(p_comment.type_of(), NodeType::CommentNode); + let NodeDataTypeInternal::Comment(p_comment_data) = &p_comment.data else { + panic!() + }; + assert_eq!(p_comment_data.value, "comment inside p"); + + // body inside p + let p_body = doc_read.node_by_id(p_children[1]).unwrap(); + assert_eq!(p_body.type_of(), NodeType::TextNode); + let NodeDataTypeInternal::Text(p_body_data) = &p_body.data else { + panic!() + }; + assert_eq!(p_body_data.value, "hey"); + + // comment inside div + let div_comment = doc_read.node_by_id(div_children[1]).unwrap(); + assert_eq!(div_comment.type_of(), NodeType::CommentNode); + let NodeDataTypeInternal::Comment(div_comment_data) = &div_comment.data else { + panic!() + }; + assert_eq!(div_comment_data.value, "comment inside div"); + + // validate ID is searchable in dom + assert_eq!(*doc_read.named_id_elements.get("myid").unwrap(), p_id); + + // validate attribute is applied to underlying element + let p_node = doc_read.node_by_id(p_id).unwrap(); + let NodeDataTypeInternal::Element(p_element) = &p_node.data else { + panic!() + }; + assert_eq!(p_element.attributes().get("id").unwrap(), "myid"); + } + + #[test] + fn insert_generic_attribute() { + let mut doc_handle: DocumentHandle, Css3System> = + >::new_document(None); + + let node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let node_id = doc_handle.get_mut().register_node_at(node, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(node_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("key", "value"); + binding.update_node(node); + } + drop(binding); + + let doc_read = doc_handle.get(); + let Some(data) = doc_read.node_by_id(node_id).unwrap().get_element_data() else { + panic!() + }; + assert_eq!(data.attributes().get("key").unwrap(), "value"); + } + + #[test] + fn task_queue_insert_generic_attribute() { + let doc_handle: DocumentHandle, Css3System> = + >::new_document(None); + + let mut task_queue = DocumentTaskQueue::new(doc_handle.clone()); + let div_id = task_queue.create_element("div", NodeId::root(), None, HTML_NAMESPACE, Location::default()); + let _ = task_queue.insert_attribute("key", "value", div_id, Location::default()); + let errors = task_queue.flush(); + assert!(errors.is_empty()); + let doc_read = doc_handle.get(); + let NodeDataTypeInternal::Element(element) = &doc_read.node_by_id(div_id).unwrap().data else { + panic!() + }; + assert_eq!(element.attributes().get("key").unwrap(), "value"); + } + + #[test] + fn insert_class_attribute() { + let mut doc_handle: DocumentHandle, Css3System> = + >::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(div_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "one two three"); + binding.update_node(node); + } + drop(binding); + + let binding = doc_handle.get(); + let NodeDataTypeInternal::Element(element_data) = &binding.node_by_id(div_id).unwrap().data else { + panic!() + }; + assert!(element_data.classlist().contains("one")); + assert!(element_data.classlist().contains("two")); + assert!(element_data.classlist().contains("three")); + } + + #[test] + fn task_queue_insert_class_attribute() { + let doc_handle: DocumentHandle, Css3System> = DocumentBuilderImpl::new_document(None); + + let mut task_queue = DocumentTaskQueue::new(doc_handle.clone()); + let div_id = task_queue.create_element("div", NodeId::root(), None, HTML_NAMESPACE, Location::default()); + let _ = task_queue.insert_attribute("class", "one two three", div_id, Location::default()); + let errors = task_queue.flush(); + println!("{:?}", errors); + assert!(errors.is_empty()); + + let binding = doc_handle.get(); + let element = binding.node_by_id(div_id).unwrap().get_element_data().unwrap(); + + assert!(element.classlist().contains("one")); + assert!(element.classlist().contains("two")); + assert!(element.classlist().contains("three")); + } + + #[test] + fn uninitialized_query() { + let doc_handle: DocumentHandle, Css3System> = DocumentBuilderImpl::new_document(None); + + let query = Query::new(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query); + if let Err(err) = found_ids { + assert_eq!( + err.to_string(), + "query: generic error: Query predicate is uninitialized" + ); + } else { + panic!() + } + } + + #[test] + fn single_query_equals_tag_find_first() { + //
+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_3, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + doc_handle.get_mut().register_node_at(p_node, NodeId::root(), None); + + let query = Query::new().equals_tag("p").find_first(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 1); + assert_eq!(found_ids, [p_id]); + } + + #[test] + fn single_query_equals_tag_find_all() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let p_node_2 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_2 = doc_handle.get_mut().register_node_at(p_node_2, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node_3 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_3 = doc_handle.get_mut().register_node_at(p_node_3, div_id_3, None); + + let p_node_4 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_4 = doc_handle.get_mut().register_node_at(p_node_4, NodeId::root(), None); + + let query = Query::new().equals_tag("p").find_all(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 4); + assert_eq!(found_ids, [p_id, p_id_2, p_id_3, p_id_4]); + } + + #[test] + fn single_query_equals_id() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let p_node_2 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_2 = doc_handle.get_mut().register_node_at(p_node_2, div_id, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_2).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("id", "myid"); + binding.update_node(node); + } + drop(binding); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_3, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, NodeId::root(), None); + + let query = Query::new().equals_id("myid").find_first(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 1); + assert_eq!(found_ids, [p_id_2]); + } + + #[test] + fn single_query_contains_class_find_first() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "one two"); + binding.update_node(node); + } + drop(binding); + + let p_node_2 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node_2, div_id, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(div_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "one"); + data.add_attribute("id", "myid"); + binding.update_node(node); + } + drop(binding); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node_3 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_3 = doc_handle.get_mut().register_node_at(p_node_3, div_id_3, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_3).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "two_tree"); + binding.update_node(node); + } + drop(binding); + + let p_node_4 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_4 = doc_handle.get_mut().register_node_at(p_node_4, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_4).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "three"); + binding.update_node(node); + } + drop(binding); + + let query = Query::new().contains_class("two").find_first(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 1); + assert_eq!(found_ids, [p_id]); + } + + #[test] + fn single_query_contains_class_find_all() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "one two"); + binding.update_node(node); + } + drop(binding); + + let p_node_2 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_2 = doc_handle.get_mut().register_node_at(p_node_2, div_id, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_2).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "one"); + data.add_attribute("id", "myid"); + binding.update_node(node); + } + drop(binding); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node_3 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_3 = doc_handle.get_mut().register_node_at(p_node_3, div_id_3, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_3).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "two three"); + binding.update_node(node); + } + drop(binding); + + let p_node_4 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_4 = doc_handle.get_mut().register_node_at(p_node_4, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_4).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("class", "three"); + binding.update_node(node); + } + drop(binding); + + let query = Query::new().contains_class("two").find_all(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 2); + assert_eq!(found_ids, [p_id, p_id_3]); + } + + #[test] + fn single_query_contains_attribute_find_first() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(div_id_2).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("id", "myid"); + data.add_attribute("style", "somestyle"); + binding.update_node(node); + } + drop(binding); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("title", "key"); + binding.update_node(node); + } + drop(binding); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(div_id_3).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("style", "otherstyle"); + data.add_attribute("id", "otherid"); + binding.update_node(node); + } + drop(binding); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_3, None); + + let p_node_4 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_4 = doc_handle.get_mut().register_node_at(p_node_4, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_4).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("title", "yo"); + data.add_attribute("style", "cat"); + binding.update_node(node); + } + drop(binding); + + let query = Query::new().contains_attribute("style").find_first(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 1); + assert_eq!(found_ids, [div_id_2]); + } + + #[test] + fn single_query_contains_attribute_find_all() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(div_id_2).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("id", "myid"); + data.add_attribute("style", "somestyle"); + binding.update_node(node); + } + drop(binding); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("title", "key"); + binding.update_node(node); + } + drop(binding); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(div_id_3).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("style", "otherstyle"); + data.add_attribute("id", "otherid"); + + binding.update_node(node); + } + drop(binding); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_3, None); + + let p_node_4 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_4 = doc_handle.get_mut().register_node_at(p_node_4, NodeId::root(), None); + + let mut binding = doc_handle.get_mut(); + let mut node = binding.cloned_node_by_id(p_id_4).unwrap(); + if let Some(data) = node.get_element_data_mut() { + data.add_attribute("title", "yo"); + data.add_attribute("style", "cat"); + + binding.update_node(node); + } + drop(binding); + + let query = Query::new().contains_attribute("style").find_all(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 3); + assert_eq!(found_ids, [div_id_2, div_id_3, p_id_4]); + } + + #[test] + fn single_query_contains_child_find_first() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_3, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, NodeId::root(), None); + + let query = Query::new().contains_child_tag("p").find_first(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 1); + assert_eq!(found_ids, [NodeId::root()]); + } + + #[test] + fn single_query_contains_child_find_all() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_3, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, NodeId::root(), None); + + let query = Query::new().contains_child_tag("p").find_all(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 4); + assert_eq!(found_ids, [NodeId::root(), div_id, div_id_2, div_id_3]); + } + + #[test] + fn single_query_has_parent_find_first() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, div_id_3, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, NodeId::root(), None); + + let query = Query::new().has_parent_tag("div").find_first(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 1); + assert_eq!(found_ids, [div_id_2]); + } + + #[test] + fn single_query_has_parent_find_all() { + //

+ //
+ //

+ //

+ //

+ //

+ //

+ let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let p_node_2 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_2 = doc_handle.get_mut().register_node_at(p_node_2, div_id, None); + + let div_node_3 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_3 = doc_handle.get_mut().register_node_at(div_node_3, NodeId::root(), None); + + let p_node_3 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_3 = doc_handle.get_mut().register_node_at(p_node_3, div_id_3, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let _ = doc_handle.get_mut().register_node_at(p_node, NodeId::root(), None); + + let query = Query::new().has_parent_tag("div").find_all(); + let found_ids = DocumentQuery::query(doc_handle.clone(), &query).unwrap(); + assert_eq!(found_ids.len(), 4); + assert_eq!(found_ids, [div_id_2, p_id, p_id_2, p_id_3]); + } + + #[test] + fn tree_iterator() { + let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + //

+ //
+ //

first p tag + //

second p tag + //

third p tag + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, div_id, None); + + let p_node = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id = doc_handle.get_mut().register_node_at(p_node, div_id_2, None); + + let text_node = Document::new_text_node(doc_handle.clone(), "first p tag", Location::default()); + let text_id = doc_handle.get_mut().register_node_at(text_node, p_id, None); + + let p_node_2 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_2 = doc_handle.get_mut().register_node_at(p_node_2, div_id_2, None); + + let text_node_2 = Document::new_text_node(doc_handle.clone(), "second p tag", Location::default()); + let text_id_2 = doc_handle.get_mut().register_node_at(text_node_2, p_id_2, None); + let p_node_3 = Document::new_element_node( + doc_handle.clone(), + "p", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let p_id_3 = doc_handle.get_mut().register_node_at(p_node_3, div_id, None); + + let text_node_3 = Document::new_text_node(doc_handle.clone(), "third p tag", Location::default()); + let text_id_3 = doc_handle.get_mut().register_node_at(text_node_3, p_id_3, None); + + let tree_iterator = TreeIterator::new(doc_handle.clone()); + + let expected_order = vec![ + NodeId::root(), + div_id, + div_id_2, + p_id, + text_id, + p_id_2, + text_id_2, + p_id_3, + text_id_3, + ]; + + let mut traversed_nodes = Vec::new(); + for current_node_id in tree_iterator { + traversed_nodes.push(current_node_id); + } + + assert_eq!(expected_order, traversed_nodes); + } + + #[test] + fn tree_iterator_mutation() { + let mut doc_handle: DocumentHandle, Css3System> = + DocumentBuilderImpl::new_document(None); + + let div_node = Document::new_element_node( + doc_handle.clone(), + "div", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id = doc_handle.get_mut().register_node_at(div_node, NodeId::root(), None); + + let mut tree_iterator = TreeIterator::new(doc_handle.clone()); + let mut current_node_id; + + current_node_id = tree_iterator.next(); + assert_eq!(current_node_id.unwrap(), NodeId::root()); + + // we mutate the tree while the iterator is still "open" + let div_node_2 = Document::new_element_node( + doc_handle.clone(), + "div_1", + Some(HTML_NAMESPACE), + HashMap::new(), + Location::default(), + ); + let div_id_2 = doc_handle.get_mut().register_node_at(div_node_2, NodeId::root(), None); + + current_node_id = tree_iterator.next(); + assert_eq!(current_node_id.unwrap(), div_id); + + // and find this node on next iteration + current_node_id = tree_iterator.next(); + assert_eq!(current_node_id.unwrap(), div_id_2); + } +} diff --git a/crates/gosub_html5/src/document/fragment.rs b/crates/gosub_html5/src/document/fragment.rs new file mode 100644 index 000000000..4853a3803 --- /dev/null +++ b/crates/gosub_html5/src/document/fragment.rs @@ -0,0 +1,63 @@ +use crate::DocumentHandle; +use core::fmt; +use core::fmt::Debug; + +use crate::document::document_impl::DocumentImpl; +use crate::node::arena::NodeArena; +use crate::node::node_impl::NodeImpl; +use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::document::DocumentFragment; + +/// Defines a document fragment which can be attached to for instance a