svgdom-0.18.0/.gitignore010064400017500001750000000000611323540723700133130ustar0000000000000000target Cargo.lock .idea svgdom.iml benches/*.svg svgdom-0.18.0/.travis.yml010064400017500001750000000003511352342261400134320ustar0000000000000000language: rust rust: - 1.32.0 - stable - nightly script: - cargo test - if [ $TRAVIS_RUST_VERSION == "nightly" ]; then env RUSTFLAGS="-Z sanitizer=leak" cargo +nightly test --target x86_64-unknown-linux-gnu; fi svgdom-0.18.0/CHANGELOG.md010064400017500001750000000521251353150444100131360ustar0000000000000000# Change Log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ## [0.18.0] - 2019-08-28 ## Changed - A completely new CSS parser. - `xmlwriter` is used for XML generation instead of a custom writer. - No need to reexport the `WriteBuffer` trait anymore. - Rename `Node::is_tag_name` into `Node::has_tag_name`. ## Removed - `DisplaySvg`. Use `Document::to_string` and `Document::to_string_with_opt` instead. - `WriteOptions::attributes_order`. - `AttributesOrder`. - `ValueWriteBuffer`. - `Document::from_str_with_opt`. - `ParseOptions`. - `PathBuilder`. ## Fixed - Parse only presentation attributes from the `style` element and attribute. ## [0.17.0] - 2019-06-12 ### Added - Parse feComposite's `k1`, `k2`, `k3` and `k4` attributes. - Parse `startOffset` attribute. ### Changed - Minimum Rust version is 1.32 now. ## [0.16.1] - 2019-03-15 ### Changed - Print warning instead of panicking during removing an already removed node. ## [0.16.0] - 2019-01-02 ### Added - `AttributeValue::Angle`. - A better `baseline-shift`, `letter-spacing` and `word-spacing` attribute parsing. - `orient`, `glyph-orientation-horizontal`, `glyph-orientation-vertical`, `refX`, `refY`, `markerWidth` and `markerHeight` attributes parsing. ### Fixed - `stroke-miterlimit` will be parsed as a `Number` and not as a `Length` now. - `length` and `number` attribute types parsing. - `offset` attribute parsing. - IRI resolving order when SVG has duplicated ID's. ## [0.15.0] - 2018-12-13 ### Added - `ParserError::InvalidAttributeValue`. - `ElementType::is_filter_primitive`. ### Changed - New error messages. ### Removed - `InvalidCSS`, `SvgTypesError`, `UnexpectedCloseTag`, `XmlError` and `CssError` from `ParserError`. ## [0.14.0] - 2018-09-12 ### Added - Implemented `Display` for `Attributes` and `Node`. - Implemented `Debug` for `ParseOptions` and `WriteOptions`. ### Changed - From now, only SVG elements and attributes will be parsed. - Split `Error` into `Error` and `ParserError`. - New `Debug` implementation for `Attributes` and `Node`. - `Attributes::new`, `Attributes::insert` and `Attributes::remove` are private now. - Rename `Attribute::default` into `new_default`. ### Removed - Namespace prefixes. - `failure` dependency. - Unused error types. - `NodeType::Declaration`, `Node::is_declaration`. - `NodeType::Cdata`, `Node::is_cdata`. - `parse_comments`, `parse_declarations`, `parse_unknown_elements`, `parse_unknown_attributes`, `parse_px_unit`, `skip_elements_crosslink`, `skip_paint_fallback` from `ParseOptions`. - `assert_eq_text` macro. - `Attributes::insert_from`. Use `Node::set_attribute` instead, - `Attributes::remove_impl`. - `Attributes::retain`. - `Node::has_attributes`. - `Attribute::check_is_default`. ## [0.13.0] - 2018-05-23 ### Added - Check for a proper element opening and closing tags. - `is_root`, `is_element`, `is_declaration`, `is_comment`, `is_cdata` and `is_text` methods to the `Node` struct. - `is_none`, `is_inherit`, `is_current_color`, `is_paint` and `is_aspect_ratio` methods to the `Attribute` struct. - Implement all `is_*` methods form the `Attribute` struct to `AttributeValue`. - `AttributeValue::Paint`. - `ElementType::is_paint_server`. - `is_link_container` to `Attribute` and `AttributeValue`. - `Node::is_detached`. - Elements from ENTITY resolving. ### Changed - FuncIRI for `fill` and `stroke` attributes will be parsed as `AttributeValue::Paint` and not as `AttributeValue::FuncLink` now. - `Document::create_node` accepts `Into` and not `&str` now. - `Declaration` node type accepts attributes now. So it will be parsed as node with attributes and not as node with text. - Not well-defined `id` attributes are allowed now. - Parse `rotate` attribute as `NumberList`. - New text preprocessing algorithm. ### Removed - `Attribute::visible` field. - `Node::has_visible_attribute`. - `WriteOptions::write_hidden_attributes`. - `Attributes::iter_svg`. Use `iter().svg()` instead. - `Attributes::iter_svg_mut`. Use `iter_mut().svg()` instead. ### Fixed - Mixed `xml:space` processing. - Empty `tspan` saving. ## [0.12.0] - 2018-04-24 ### Added - `ParseOptions::skip_elements_crosslink`. - Implemented `WriteBuffer` and `Display` for `QName`. ### Changed - All SVG types implementation move to the `svgtypes` crate. - `Node::set_attribute_if_none` accepts `Into` now. ### Removed - `ValueId` type. Now only `none`, `inherit` and `currentColor` will be stored not as strings. ## [0.11.1] - 2018-04-12 - Moved to `rctree` as a tree backend. ## [0.11.0] - 2018-04-10 ### Added - Implemented `Deref` and `DerefMut` for `Path`. - `AttributeValue::Points`. - `AttributeValue::ViewBox`. - `AttributeValue::AspectRatio`. ### Changed - Moved to `failure`. - Moved to `rcc-tree` from own tree implementation. - Relicense from MPL-2.0 to MIT/Apache-2.0. - Minimal Rust version is 1.18. - `Path`'s fields are private now. Use `Deref` instead. - `Attribute::name` is `QName` and not `Name` now. So it has namespace prefix in it. Example: ```rust // before node.set_attribute(AttributeId::XmlSpace, "preserve"); // from now node.set_attribute(("xml", AttributeId::Space)), "preserve"); ``` - `Name::into_ref` to `Name::as_ref`. - `Node::tag_name` returns `Ref` and not `Option>` now. - Rename `Node::parents` to `ancestors`. - `Node::ancestors`(parents) starts with a current node now and returns the root node too. ### Removed - `AttributeValue::name`. - `Node::document`. - `Node::remove`. Use `Document::remove_node`. - `Node::remove_attributes`. - `Node::remove_attributes`. - `Node::make_copy`. Use `Document::copy_node`. - `Node::make_copy_deep`. Use `Document::copy_node_deep`. - `Document::first_child`. - `Document::append`. - `Document::descendants`. - `Document::children`. - `LinkedNodes` iterator. ## [0.10.5] - 2018-04-10 ### Changed - A default `Transform` will be printed as `matrix(1 0 0 1 0)` and not as an empty string. ### Fixed - Text with `xml:space` preprocessing. ## [0.10.4] - 2018-02-03 ### Fixed - Invalid files in the crate package. ## [0.10.3] - 2018-01-29 ### Fixed - Memory leak. - Stack overflow when `Document` has a lot of nodes (>100k). ## [0.10.2] - 2018-01-23 ### Fixed - `WriteOptions::remove_duplicated_path_commands` ignores `MoveTo` now. ## [0.10.1] - 2018-01-17 ### Fixed - `marker` property resolving from CSS. ## [0.10.0] - 2018-01-17 **Note:** this update contain breaking changes. ### Added - `WriteOptions::list_separator`. - `WriteOptions::attributes_order`. - Implemented `WriteBuffer` and `ToStringWithOptions` for `NumberList` and `LengthList`. - Quotes escape in attribute values. ### Changed - The `types` module is private now and all types are available in the global namespace. - `WriteOptionsPaths` merged to `WriteOptions`. - The value of the `unicode` attribute is always escaped now. - The minimal Rust version is 1.16 now. Because of `log`. ## [0.9.1] - 2017-12-15 ### Fixed - Text saving. ## [0.9.0] - 2017-12-15 **Note:** this update contain breaking changes. ### Added - `Node::set_attribute_if_none`. - Better text parsing. - Implemented `AttributeType` for `AttributeId`. - `Option::skip_invalid_attributes`. - `Option::skip_invalid_css`. - `Option::skip_paint_fallback`. ### Changed - `Descendants::svg`, `Children::svg` and `Parents::svg` returns `(ElementId, Node)` instead of `Node` now. - Errors implemented via `error-chain` now. - Quotes inside text nodes no longer escaped. - All warnings will be printed with `warn!` macro from the `log` crate now. - Rename `FromFrame` to `ParseFromSpan`. ### Removed - `postproc` module. ## [0.8.1] - 2017-10-02 ### Fixed - Memory leak. ## [0.8.0] - 2017-09-30 ### Changed - Rename `FromStream` into `FromFrame`. - `FromFrame` no longer implements the `from_str` method and inherits from `FromStr` instead. - Rename `WriteToString` into `ToStringWithOptions`. ### Removed - `parsing` build feature. ## [0.7.0] - 2017-09-26 ### Added - `FuzzyEq::is_fuzzy_zero`. - `Node::parents_with_self`. - `WriteOptions::attributes_indent`. - `Length::new_number`. - Text escaping before saving to file. - **Breaking change.** Enforced mutability. Many `Document`'s and `Node`'s methods require `&mut self` now. This will prevent many runtime errors caused by borrowing `Rc` as mutable more than once. - `Debug` for `Attributes`. ### Changed - `writer` module is private now. - **Breaking change.** `Node::set_attribute` accepts only `Attribute` or tuple with attribute name and value now. - `Attributes` methods: `insert`, `remove` and `retain` will panic on an invalid input in debug mode now. - `postproc::resolve_stop_attributes` no longer converts `offset` attribute into a `Number` type, leaving it in a `Length` type. - **Breaking change.** This methods are require `&mut self` now: - `Document`: create_element, create_node, append, drain. - `Node`: detach, remove, drain, append, prepend, insert_after, insert_before, text_mut, set_text, set_id, set_tag_name, attributes_mut, set_attribute, set_attribute_checked, remove_attribute, remove_attributes - `postproc`: fix_rect_attributes, fix_poly_attributes - Default numeric precision is 11 instead of 12 now. ### Removed - `Node::attribute`. Use `node.attributes().get()` instead. - `Node::attribute_value`. Use `node.attributes().get_value()` instead. - `Node::has_attribute_with_value`. - `Node::set_link_attribute`. Use `Node::set_attribute` instead. - `Node::set_attribute_object`. Use `Node::set_attribute` instead. - All `AttributeValue::as_*` methods. - `Document::default`, because it was useless. ### Fixed - `postproc::resolve_stop_attributes` can be executed multiple times without errors now. - Additional whitespace after command in paths. ## [0.6.0] - 2017-06-18 ### Added - `Node::text_mut`. - New text processing algorithm. Better `xml:space` support. ### Changed - `postproc::resolve_inherit` doesn't return `Result` now. Any unresolved attributes will trigger a warning now. - Node's text is stored as `String` and not as `Option` now. - `Node::text` returns `Ref` now. - Text will be preprocessed according to the [spec](https://www.w3.org/TR/SVG11/text.html#WhiteSpace) now. ### Removed - `Error::UnresolvedAttribute`. ### Fixed - Additional whitespace during ArcTo writing. ## [0.5.0] - 2017-06-05 ### Added - `postproc` module. - `ElementType::is_gradient`. - `Indent` enum instead of `i8` for `WriteOptions::indent`. - Implemented `Display` trait for `path::Segment`. - `path::Segment::fuzzy_eq`. - `Transform::fuzzy_eq`. ### Changed - All warnings will be printed to stderr now. - `FromStream::from_data`, `Document::from_data`, `Document::from_data_with_opt` accepts `&str` instead of `&[u8]` now. - Rename `FromStream::from_data` to `FromStream::from_str`. - Rename `Document::from_data` to `Document::from_str`. - Rename `Document::from_str_with_opt` to `Document::from_str_with_opt`. - `Transform` uses default `PartialEq` implementation and not one with `FuzzyEq` now. Use `Transform::fuzzy_eq` method to get old result. ### Removed - `Error::Utf8Error`, because `Document::from_data` accepts `&str` now. - `WriteOptions::paths::coordinates_precision`. ## [0.4.0] - 2017-03-15 ### Added - `Node::make_copy` and `Node::make_deep_copy`. - `Error::InvalidEncoding` and `Error::Utf8Error`. - Input stream encoding validation. ### Changed - `Node::prepend`, `Node::insert_after`, `Node::insert_before` accepts `&Node` now. ### Fixed - Memory leak in `Document`. `Document` children were never deleted because of `Rc` crosslink. ## [0.3.1] - 2017-02-01 ### Changed - Use specific version of dependencies. ## [0.3.0] - 2017-01-14 ### Added - `AttributeValue::name`. - `Length::zero`. - `WriteOptions::paths::use_implicit_lineto_commands`. - `WriteOptions::paths::coordinates_precision`. - `FuzzyOrd` trait for `f64`. - `Transform::apply_ref`. - An external CSS parser, which brings support for universal and id selectors. - Check that `style` element has a valid `type` attribute value. - `parsing` build feature. - `ElementType` trait for `Node`. - `AttributeType` trait for `Attribute`. ### Changed - Default numeric precision is 12 instead of 8 now. - Float comparison is done using [float-cmp](https://crates.io/crates/float-cmp). - `Node::set_attribute_object` now handles links. - `Transform`'s `translate`, `scale`, `rotate`, `skew_x` and `skew_y` methods no longer consuming and modifies itself. - `Node::is_referenced`, `Node::is_basic_shape` and `Node::is_container` moved to `ElementType` trait. - Most of the `Attribute`'s `is_*` methods moved to `AttributeType` trait. - Default transform matrices are not added to the DOM anymore. - Empty list-based attributes are not added to the DOM anymore. ### Fixed - `Transform`'s `rotate`, `skew_x` and `skew_y` methods doesn't worked correctly. ### Removed - Custom SVG writer support and custom_writer example. - `Attributes::get_value_or`. Use `Attributes::get_value().unwrap_or()` instead. ## [0.2.0] - 2016-11-04 ### Added - `Node::drain` method to remove nodes by the predicate without memory allocations. - `Node::parents` - an iterator of `Node`s to the parents of a given node. - Added support for implementing a custom SVG writer. See the `custom_writer` example for details. - `Attributes::iter_svg_mut`. - Default value for `clip` attribute. - `path::Path::with_capacity` and `path::Builder::with_capacity`. - `ParseOptions::skip_unresolved_classes`. ### Changed - Always add a space after ArcTo flags during the path writing. - SVG and non-SVG attributes now stored in the same container and not separately. - Rename `LinkAttributes` to `LinkedNodes`. - `descendants` and `children` methods now returns all nodes and not only SVG elements. Use the `svg()` method to get only SVG elements. Example: `descendants().svg()`. - Rename `has_children_nodes` to `has_children`. - The `LinkedNodes` iterator contains a reference to nodes vec and not a copy now. It will break node modifying while iterating. Less useful, but more correct. - The `Document::create_element` accepts `&str` now. - The `Document::create_element` will panic now if supplied string-based tag name is empty. - The `Node::set_tag_name` will panic now if supplied string-based tag name is empty. - The `write` module renamed to `writer` and made public. - Attribute modules moved to `attribute` submodule. Doesn't impact API. ### Removed - `descendant_nodes` and `children_nodes` methods. - `Descendants::skip_children`. - `ParseOptions::skip_svg_elements`. - `Node::same_node`. - `Document::create_nonsvg_element`. Use `Document::create_element` instead. - `Node::set_tag_id`. Use `Node::set_tag_name` instead. - `Node::is_tag_id`. Use `Node::is_tag_name` instead. - `Node::has_child_with_tag_name`. Can be easily implemented with iterators. - `Node::child_by_tag_name`. Can be easily implemented with iterators. - `Node::child_by_tag_id`. Can be easily implemented with iterators. - `Node::parent_element`. Can be easily implemented with iterators. - `Node::parent_attribute`. Can be easily implemented with iterators. - The `EmptyTagName` error type. ### Fixed - `ParseOptions::parse_px_unit` now works in `LengthList`. - CSS processing when style defined multiple times. ## [0.1.0] - 2016-10-09 ### Added - Missing license headers. - The `children` method for the `Document`. - The `is_inheritable` method for the `Attribute`. - The `get_value_mut` method for the `Attributes`. - `children_nodes`, `is_container`, `set_text`, methods for the `Node`. - `has_translate`, `has_scale`, `has_proportional_scale`, `has_skew`, `has_rotate`, `get_translate`, `get_scale`, `get_skew`, `get_rotate`, `apply`, `rotate`, `skew_x`, `skew_y` methods to the `Transform`. - `clip` and `font` attributes to the presentation attributes list. - The `types::number::FuzzyEq` trait. - A new error type: `EmptyTagName`. ### Changed - More correct CSS2 processing. - Rename `is_element` method into `is_svg_element` in the `Node`. - Rename `to_absolute` method into `conv_to_absolute` in the `Path`. - Rename `to_relative` method into `conv_to_relative` in the `Path`. - Rename `descendants_all` method into `descendant_nodes` in the `Node`. - Rename `get_or` method into `get_value_or` in the `Attributes`. - The `children` method of the `Node` struct now returns an iterator over SVG elements and not all nodes. For all nodes you should use `children_nodes` method now. - The `has_children` method now returns true if node has children elements, not nodes. For nodes you should use `has_children_nodes` method now. - Remove redundant semicolon from error messages. - We keep unknown attributes from styles now. - Broken FuncIRI inside `fill` attributes now replaces with `none`. - The `WriteOptions::numbers::remove_leading_zero` move to `WriteOptions::remove_leading_zero`. - The `WriteOptions::transforms::simplify_matrix` move to `WriteOptions::simplify_transform_matrices`. - Split the `Document::create_element` method into two: `create_element` and `create_nonsvg_element`. - Split the `Node::set_tag_name` method into two: `set_tag_id` and `set_tag_name`. ### Fixed - Attributes from ENTITY is now parsed and not inserted as is. - `parse_unknown_attributes` flag doesn't processed correctly. - ArcTo segment writing. ### Removed - The `first_element_child` method from the `Document`. Use `doc.children().nth(0)` instead. - `WriteOptions::numbers`. The precision is fixed now. - The `find_reference_attribute` method from the `Node`. ## [0.0.3] - 2016-09-20 ### Added - A fallback value processing from the \ type. - `has_attributes`, `remove`, `is_basic_shape`, `has_visible_attribute` methods to the `Node`. - `is_graphical_event`, `is_conditional_processing`, `is_core`, `is_fill`, `is_stroke`, `is_animation_event`, `is_document_event`, methods to the `Attribute`. - `types::path::Segment` struct which is used instead of one from `libsvgparser`. - `to_absolute` and `to_relative` methods to the `types::path::Path`. - New error type: `BrokenFuncIri`. - `is_*type*` methods to the `Attribute`. Like `is_number`, etc. ### Changed - Moved back from `dtoa` to the std implementation. - The `Transform` struct is now implements Copy. - Nodes should be removed via `Node::remove` method and not via `Node::detach` + Drop. - `Attributes` implemented using `Vec` and not `VecMap` now. It's much faster. - Split `AttributeValue::Link` into `AttributeValue::Link` and `AttributeValue::FuncLink`. ### Fixed - Fix crash in the NodeData's Drop. - Fix attributes remove which contains links to removed node. - Fix parsing of the empty `style` element. ## [0.0.2] - 2016-09-09 ### Added - `first_element_child`, `svg_element`, `create_element_with_text` methods to the `Document`. - `has_parent`, `has_text_children`, `document` methods to the `Node`. ### Changed - Use `dtoa::write()` instead of `write!()`. - `Document::append` now returns added node. ### Fixed - Fix default value of the 'stroke-miterlimit' attribute. - Fix text generating when parent 'text' element has only one text node. ## 0.0.1 - 2016-08-26 ### Added - Initial release. [Unreleased]: https://github.com/RazrFalcon/svgdom/compare/v0.18.0...HEAD [0.18.0]: https://github.com/RazrFalcon/svgdom/compare/v0.17.0...v0.18.0 [0.17.0]: https://github.com/RazrFalcon/svgdom/compare/v0.16.1...v0.17.0 [0.16.1]: https://github.com/RazrFalcon/svgdom/compare/v0.16.0...v0.16.1 [0.16.0]: https://github.com/RazrFalcon/svgdom/compare/v0.15.0...v0.16.0 [0.15.0]: https://github.com/RazrFalcon/svgdom/compare/v0.14.0...v0.15.0 [0.14.0]: https://github.com/RazrFalcon/svgdom/compare/v0.13.0...v0.14.0 [0.13.0]: https://github.com/RazrFalcon/svgdom/compare/v0.12.0...v0.13.0 [0.12.0]: https://github.com/RazrFalcon/svgdom/compare/v0.11.1...v0.12.0 [0.11.1]: https://github.com/RazrFalcon/svgdom/compare/v0.11.0...v0.11.1 [0.11.0]: https://github.com/RazrFalcon/svgdom/compare/v0.10.5...v0.11.0 [0.10.5]: https://github.com/RazrFalcon/svgdom/compare/v0.10.4...v0.10.5 [0.10.4]: https://github.com/RazrFalcon/svgdom/compare/v0.10.3...v0.10.4 [0.10.3]: https://github.com/RazrFalcon/svgdom/compare/v0.10.2...v0.10.3 [0.10.2]: https://github.com/RazrFalcon/svgdom/compare/v0.10.1...v0.10.2 [0.10.1]: https://github.com/RazrFalcon/svgdom/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/RazrFalcon/svgdom/compare/v0.9.1...v0.10.0 [0.9.1]: https://github.com/RazrFalcon/svgdom/compare/v0.9.0...v0.9.1 [0.9.0]: https://github.com/RazrFalcon/svgdom/compare/v0.8.1...v0.9.0 [0.8.1]: https://github.com/RazrFalcon/svgdom/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/RazrFalcon/svgdom/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/RazrFalcon/svgdom/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/RazrFalcon/svgdom/compare/v0.5.0...v0.6.0 [0.5.0]: https://github.com/RazrFalcon/svgdom/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/RazrFalcon/svgdom/compare/v0.3.1...v0.4.0 [0.3.1]: https://github.com/RazrFalcon/svgdom/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/RazrFalcon/svgdom/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/RazrFalcon/svgdom/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/RazrFalcon/svgdom/compare/0.0.3...v0.1.0 [0.0.3]: https://github.com/RazrFalcon/svgdom/compare/0.0.2...0.0.3 [0.0.2]: https://github.com/RazrFalcon/svgdom/compare/0.0.1...0.0.2 svgdom-0.18.0/Cargo.toml.orig010064400017500001750000000014151353150455700142200ustar0000000000000000[package] name = "svgdom" # When updating version, also modify html_root_url in the lib.rs version = "0.18.0" authors = ["Evgeniy Reizner "] keywords = ["svg", "dom"] categories = ["parser-implementations"] license = "MIT/Apache-2.0" edition = "2018" description = "[DEPRECATED] Library to represent an SVG as a DOM." repository = "https://github.com/RazrFalcon/svgdom" documentation = "https://docs.rs/svgdom/" readme = "README.md" [dependencies] log = "0.4.5" roxmltree = "0.6" simplecss = "0.2" siphasher = "0.2.3" slab = "0.4" svgtypes = "0.5" xmlwriter = "0.1" [dev-dependencies] bencher = "0.1" fern = "0.5" pretty_assertions = "0.6" time = "0.1" [[bench]] name = "parser" harness = false [lib] path = "src/lib.rs" # for cargo-readme doctest = true svgdom-0.18.0/Cargo.toml0000644000000026700000000000000104630ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "svgdom" version = "0.18.0" authors = ["Evgeniy Reizner "] description = "[DEPRECATED] Library to represent an SVG as a DOM." documentation = "https://docs.rs/svgdom/" readme = "README.md" keywords = ["svg", "dom"] categories = ["parser-implementations"] license = "MIT/Apache-2.0" repository = "https://github.com/RazrFalcon/svgdom" [lib] path = "src/lib.rs" doctest = true [[bench]] name = "parser" harness = false [dependencies.log] version = "0.4.5" [dependencies.roxmltree] version = "0.6" [dependencies.simplecss] version = "0.2" [dependencies.siphasher] version = "0.2.3" [dependencies.slab] version = "0.4" [dependencies.svgtypes] version = "0.5" [dependencies.xmlwriter] version = "0.1" [dev-dependencies.bencher] version = "0.1" [dev-dependencies.fern] version = "0.5" [dev-dependencies.pretty_assertions] version = "0.6" [dev-dependencies.time] version = "0.1" svgdom-0.18.0/LICENSE-APACHE010064400017500001750000000251371322753041200132530ustar0000000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. svgdom-0.18.0/LICENSE-MIT010064400017500001750000000020441324355564700127720ustar0000000000000000Copyright (c) 2018 Evgeniy Reizner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. svgdom-0.18.0/README.md010064400017500001750000000123131353151151500125770ustar0000000000000000## svgdom [![Build Status](https://travis-ci.org/RazrFalcon/svgdom.svg?branch=master)](https://travis-ci.org/RazrFalcon/svgdom) [![Crates.io](https://img.shields.io/crates/v/svgdom.svg)](https://crates.io/crates/svgdom) [![Documentation](https://docs.rs/svgdom/badge.svg)](https://docs.rs/svgdom) *svgdom* is an [SVG Full 1.1](https://www.w3.org/TR/SVG/) processing library, which allows you to parse, manipulate, generate and write an SVG content. ### Deprecation This library was an attempt to create a generic SVG DOM which can be used by various applications. But it the end it turned out that it's easier and faster to use [roxmltree](https://github.com/RazrFalcon/roxmltree) + [svgtypes](https://github.com/RazrFalcon/svgtypes) to extract only the data you need. There are two main problems with `svgdom`: 1. You can't make a nice API with a Vec-based tree and you can't have a safe API with an Rc-tree. The current implementation uses so-called Rc-tree, which provides a nice API, but all the checks are done in the runtime, so you can get a panic quite easily. It's also hard/verbose to make immutable nodes. You essentially need two types of nodes: one for immutable and one for mutable "references". A Vec-based tree would not have such problems, but you can't implement the simplest operations with it, like copying an attribute from one node to another since you have to have a mutable and an immutable references for this. And Rust forbids this. So you need some sort of generational indexes and so on. This solution is complicated in its own way. Performance is also in question, since inserting/removing an object in the middle of a Vec is expensive. 2. The SVG parsing itself is pretty complex too. There are a lot of ways you can implement it. `svgdom` creates a custom Rc-tree where all the attributes are stored as owned data. This requires a lot of allocations (usually unnecessary). The parsing/preprocessing algorithm itself can be found in [docs/preprocessor.md](docs/preprocessor.md) The problem with it is that you can't tweak it. And in many cases, it produces results that you do not need or do not expect. `svgdom` was originally used by [svgcleaner](https://github.com/RazrFalcon/svgcleaner) and [resvg](https://github.com/RazrFalcon/resvg) and both of these projects are no longer using it. ### Purpose *svgdom* is designed to simplify generic SVG processing and manipulations. Unfortunately, an SVG is very complex format (PDF spec is 826 pages long), with lots of features and implementing all of them will lead to an enormous library. That's why *svgdom* supports only a static subset of an SVG. No scripts, external resources and complex CSS styling. Parser will convert as much as possible data to a simple doc->elements->attributes structure. For example, the `fill` parameter of an element can be set: as an element's attribute, as part of a `style` attribute, inside a `style` element as CSS2, inside an `ENTITY`, using a JS code and probably with lots of other methods. Not to mention, that the `fill` attribute supports 4 different types of data. With `svgdom` you can just use `node.has_attribute(AttributeId::Fill)` and don't worry where this attribute was defined in the original file. Same goes for transforms, paths and other SVG types. The main downside of this approach is that you can't save an original formatting and some data. See the [preprocessor](https://github.com/RazrFalcon/svgdom/blob/master/docs/preprocessor.md) doc for details. ### Benefits - The element link(IRI, FuncIRI) is not just a text, but an actual link to another node. - At any time you can check which elements linked to the specific element. See `Node`'s doc for details. - Support for many SVG specific data types like paths, transforms, IRI's, styles, etc. Thanks to [svgtypes](https://github.com/RazrFalcon/svgtypes). - A complete support of text nodes: XML escaping, `xml:space`. - Fine-grained control over the SVG output. ### Limitations - Only SVG elements and attributes will be parsed. - Attribute values, CDATA with CSS, DOCTYPE, text data and whitespaces will not be preserved. - UTF-8 only. - Only most popular attributes are parsed, other stored as strings. - No compressed SVG (.svgz). You should decompress it by yourself. - CSS support is minimal. - SVG 1.1 Full only (no 2.0 Draft, Basic, Tiny subsets). ### Differences between svgdom and SVG spec - Library follows SVG spec in the data parsing, writing, but not in the tree structure. - Everything is a `Node`. There are no separated `ElementNode`, `TextNode`, etc. You still have all the data, but not in the specific *struct's*. You can check the node type via `Node::node_type()`. ### Dependency [Rust](https://www.rust-lang.org/) >= 1.32 ### License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. svgdom-0.18.0/README.tpl010064400017500001750000000015731347332025400130070ustar0000000000000000## {{crate}} [![Build Status](https://travis-ci.org/RazrFalcon/{{crate}}.svg?branch=master)](https://travis-ci.org/RazrFalcon/{{crate}}) [![Crates.io](https://img.shields.io/crates/v/{{crate}}.svg)](https://crates.io/crates/{{crate}}) [![Documentation](https://docs.rs/{{crate}}/badge.svg)](https://docs.rs/{{crate}}) {{readme}} ### Dependency [Rust](https://www.rust-lang.org/) >= 1.32 ### License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. svgdom-0.18.0/benches/README.md010064400017500001750000000012321341321773700142140ustar0000000000000000### Performance There will be no comparisons with other XML parsers since they do not parse SVG data. And no comparisons with other SVG parsers, since there are no such. Note that most of the time is spent during string to number and number to string conversion. ``` test parse_large ... bench: 20,839,388 ns/iter (+/- 169,014) test parse_medium ... bench: 2,969,591 ns/iter (+/- 42,878) test parse_small ... bench: 60,118 ns/iter (+/- 72) test write_large ... bench: 12,549,372 ns/iter (+/- 14,983) test write_medium ... bench: 1,173,646 ns/iter (+/- 2,062) test write_small ... bench: 24,401 ns/iter (+/- 85) ``` Tested on i5-3570k 3.4GHz. svgdom-0.18.0/benches/load.py010075500017500001750000000012471316251444200142310ustar0000000000000000#!/usr/bin/python3 import urllib.request import hashlib items = [ ("https://upload.wikimedia.org/wikipedia/commons/0/02/SVG_logo.svg", "small.svg", "71bbb30ab760b8f4da07639f4eeb32d6"), ("http://www.clker.com/cliparts/6/8/2/1/12344034191822607300Inkscape_skull_corneum.svg", "medium.svg", "5fcb9790c90b9362ff77daf062f963c9"), ("https://openclipart.org/download/231513/Colorful-Geometric-Line-Art-2.svg", "large.svg", "7d44ac52a89feca40f27d5024141d6cf"), ] for item in items: urllib.request.urlretrieve(item[0], item[1]) md5 = hashlib.md5(open(item[1], 'rb').read()).hexdigest() if md5 != item[2]: raise Exception("invalid hash") svgdom-0.18.0/benches/parser.rs010064400017500001750000000030361316451023700145750ustar0000000000000000#[macro_use] extern crate bencher; extern crate svgdom; use std::fs; use std::env; use std::io::Read; use bencher::Bencher; use svgdom::{Document, WriteBuffer}; const TEN_MIB: usize = 10 * 1024 * 1024; fn load_file(path: &str) -> String { let path = env::current_dir().unwrap().join(path); let mut file = fs::File::open(&path).unwrap(); let mut text = String::new(); file.read_to_string(&mut text).unwrap(); text } macro_rules! do_parse { ($name:ident, $path:expr) => ( fn $name(bencher: &mut Bencher) { let text = load_file($path); bencher.iter(|| { let _ = Document::from_str(&text).unwrap(); }) } ) } do_parse!(parse_small, "benches/small.svg"); do_parse!(parse_medium, "benches/medium.svg"); do_parse!(parse_large, "benches/large.svg"); macro_rules! do_write { ($name:ident, $path:expr) => ( fn $name(bencher: &mut Bencher) { let text = load_file($path); let doc = Document::from_str(&text).unwrap(); let mut ouput_data = Vec::with_capacity(TEN_MIB); bencher.iter(|| { doc.write_buf(&mut ouput_data); ouput_data.clear(); }) } ) } do_write!(write_small, "benches/small.svg"); do_write!(write_medium, "benches/medium.svg"); do_write!(write_large, "benches/large.svg"); benchmark_group!(benches1, parse_small, parse_medium, parse_large); benchmark_group!(benches2, write_small, write_medium, write_large); benchmark_main!(benches1, benches2); svgdom-0.18.0/docs/preprocessor.md010064400017500001750000000105041334613573000153250ustar0000000000000000# SVG DOM preprocessing Unlike the usual XML DOM implementations, `svgdom` will preprocess the document/tree a lot. ## XML parsing `svgdom` uses [`roxmltree`](https://github.com/RazrFalcon/roxmltree) as an XML parser. You can find its parsing details [here](https://github.com/RazrFalcon/roxmltree/blob/master/docs/parsing.md). ## Non-SVG elements and attributes Only SVG 1.1 elements and attributes will be parsed. But it's possible to write custom elements and attributes. ## `style` attributes splitting From: ```xml ``` to: ```xml ``` ## Text unescaping All [character references](https://www.w3.org/TR/xml/#NT-CharRef) will be resolved. Not only simple one like `&` but also any hexadecimal and decimal. Also, all whitespaces will be replaced with the Space character. Even escaped one. The text data from the SVG like this: ```xml &@@& ``` will be represented as `'@@'` and saved as: ```xml &@@& ``` ## Whitespaces trimming The text data from the SVG like this: ```xml Text ``` will be represented just as `Text` and not as `␣␣␣␣␣␣␣␣␣Text␣␣␣␣␣`. `svgdom` also supports `xml:space` attribute. So the text data from the SVG like this: ```xml Text Text Text ``` will be represented as `Text␣␣␣Text␣␣Text`. And saved as: ```xml Text Text Text ``` Note that nested `xml:space` is mostly an undefined behavior and every XML DOM implementation will process it differently. `svgdom` follows the Chrome behavior. But Firefox, for example, will process the SVG above as `Text␣␣␣Text␣␣␣Text`. ## CSS resolving `svgdom` supports only a tiny fraction of the CSS 2.1 features. If the SVG contains an unsupported CSS, it will lead to a parsing error unless the `ParseOptions::skip_invalid_css` is set. After the preprocessing the `style` elements will removed. From: ```xml ``` to: ```xml ``` The proper style resolving order is supported too. From: ```xml ``` to: ```xml ``` ## Paint fallback resolving SVG allows specifying the paint fallback value in case of an invalid FuncIRI. For example: ```xml ``` `svgdom` will convert it into: ```xml ``` because there are no elements with a `gradient1` ID. But more complex cases, like a reference to an invalid element, should be resolved manually. For example: ```xml ``` This will be represented as is, even though that `linearGradient` is invalid (because have no children). ## Crosslink resolving If an element is linked to itself, directly or indirectly, it may lead to a recursion/endless loop. `svgdom` will resolve some simple cases by default. In the example below the `lg1` gradient is linked to itself indirectly via the `xlink:href` attribute. From: ```xml ``` to: ```xml ``` More complex cases should be resolved manually. Like: ```xml ``` ## Namespaces There is only one namespace: http://www.w3.org/2000/svg . All namespaces will be resolved and removed. So SVG like this: ```xml ``` will became: ```xml ``` `xmlns` and `xmlns:xlink` attributes/namespaces will be added automatically. http://www.w3.org/1999/xlink is a special case and will be handled too. svgdom-0.18.0/examples/count_curveto_segments.rs010064400017500001750000000016061353150417000203120ustar0000000000000000use std::env; use std::fs; use svgdom::{AttributeId, AttributeValue, Document, ElementId, FilterSvg, PathCommand}; fn main() -> Result<(), Box> { let args: Vec<_> = env::args().collect(); if args.len() != 2 { println!("Usage:\n\tcount_curveto_segments in.svg"); std::process::exit(1); } let input_data = fs::read_to_string(&args[1])?; let doc = Document::from_str(&input_data).unwrap(); let mut count = 0; for (id, node) in doc.root().descendants().svg() { if id == ElementId::Path { let attrs = node.attributes(); if let Some(&AttributeValue::Path(ref path)) = attrs.get_value(AttributeId::D) { count += path.iter().filter(|seg| seg.cmd() == PathCommand::CurveTo).count(); } } } println!("This file contains {} CurveTo segments.", count); Ok(()) } svgdom-0.18.0/examples/resave.rs010064400017500001750000000013641353150416400147770ustar0000000000000000use std::env; use std::fs; fn main() -> Result<(), Box> { fern::Dispatch::new() .format(|out, message, record| out.finish(format_args!("{}: {}", record.level(), message)) ).chain(std::io::stderr()).apply()?; let start = time::precise_time_ns(); let args: Vec<_> = env::args().collect(); if args.len() != 3 { println!("Usage:\n\tresave in.svg out.svg"); std::process::exit(1); } let input_data = fs::read_to_string(&args[1])?; let doc = svgdom::Document::from_str(&input_data)?; fs::write(&args[2], doc.to_string().as_bytes())?; let end = time::precise_time_ns(); println!("Elapsed: {:.4}ms", (end - start) as f64 / 1_000_000.0); Ok(()) } svgdom-0.18.0/src/attribute.rs010064400017500001750000000045671351363061600145000ustar0000000000000000use std::fmt; use crate::{ AttributeId, AttributeQName, AttributeQNameRef, AttributeValue, QName, }; /// Representation of the SVG attribute object. #[derive(Clone, PartialEq, Debug)] pub struct Attribute { /// Attribute name. pub name: AttributeQName, /// Attribute value. pub value: AttributeValue, } // TODO: fix docs macro_rules! impl_is_type { ($name:ident) => ( #[allow(missing_docs)] pub fn $name(&self) -> bool { self.value.$name() } ) } impl Attribute { /// Constructs a new attribute. pub fn new<'a, N, T>(name: N, value: T) -> Attribute where AttributeQNameRef<'a>: From, AttributeValue: From { Attribute { name: AttributeQNameRef::from(name).into(), value: AttributeValue::from(value), } } /// Constructs a new attribute with a default value, if it known. pub fn new_default(id: AttributeId) -> Option { match AttributeValue::default_value(id) { Some(v) => Some(Attribute::new(id, v)), None => None, } } /// Returns an SVG attribute ID. pub fn id(&self) -> Option { match self.name { QName::Id(id) => Some(id), QName::Name(_) => None, } } /// Returns `true` if the attribute has the selected ID. pub fn has_id(&self, id: AttributeId) -> bool { self.name.has_id(id) } /// Returns `true` if the attribute is an SVG attribute. pub fn is_svg(&self) -> bool { match self.name { QName::Id(_) => true, QName::Name(_) => false, } } impl_is_type!(is_none); impl_is_type!(is_inherit); impl_is_type!(is_current_color); impl_is_type!(is_aspect_ratio); impl_is_type!(is_color); impl_is_type!(is_length); impl_is_type!(is_length_list); impl_is_type!(is_link); impl_is_type!(is_func_link); impl_is_type!(is_paint); impl_is_type!(is_number); impl_is_type!(is_number_list); impl_is_type!(is_path); impl_is_type!(is_points); impl_is_type!(is_string); impl_is_type!(is_transform); impl_is_type!(is_viewbox); impl_is_type!(is_link_container); } impl fmt::Display for Attribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}='{}'", self.name, self.value) } } svgdom-0.18.0/src/attribute_type.rs010064400017500001750000000214061347344453300155360ustar0000000000000000use crate::{ Attribute, AttributeId, }; /// This trait contains methods that check attribute's type according to the /// [SVG spec](https://www.w3.org/TR/SVG/intro.html#Definitions). pub trait AttributeType { /// Returns `true` if the current attribute is part of /// [presentation attributes](https://www.w3.org/TR/SVG/propidx.html). fn is_presentation(&self) -> bool; /// Returns `true` if the current attribute is part of inheritable /// [presentation attributes](https://www.w3.org/TR/SVG/propidx.html). fn is_inheritable(&self) -> bool; /// Returns `true` if the current attribute is part of /// [animation event attributes](https://www.w3.org/TR/SVG/intro.html#TermAnimationEventAttribute). fn is_animation_event(&self) -> bool; /// Returns `true` if the current attribute is part of /// [graphical event attributes](https://www.w3.org/TR/SVG/intro.html#TermGraphicalEventAttribute). fn is_graphical_event(&self) -> bool; /// Returns `true` if the current attribute is part of /// [document event attributes](https://www.w3.org/TR/SVG/intro.html#TermDocumentEventAttribute). fn is_document_event(&self) -> bool; /// Returns `true` if the current attribute is part of /// [conditional processing attributes /// ](https://www.w3.org/TR/SVG/intro.html#TermConditionalProcessingAttribute). fn is_conditional_processing(&self) -> bool; /// Returns `true` if the current attribute is part of /// [core attributes](https://www.w3.org/TR/SVG/intro.html#TermCoreAttributes). /// /// **NOTE:** the `id` attribute is part of core attributes, but we don't store /// it in `Attributes` since it's part of the `Node` struct. fn is_core(&self) -> bool; /// Returns `true` if the current attribute is part of fill attributes. /// /// List of fill attributes: `fill`, `fill-opacity`, `fill-rule`. /// /// This check is not defined by the SVG spec. fn is_fill(&self) -> bool; /// Returns `true` if the current attribute is part of stroke attributes. /// /// List of stroke attributes: `stroke`, `stroke-dasharray`, `stroke-dashoffset`, /// `stroke-dashoffset`, `stroke-linecap`, `stroke-linejoin`, `stroke-miterlimit`, /// `stroke-opacity`, `stroke-width`. /// /// This check is not defined by the SVG spec. fn is_stroke(&self) -> bool; } macro_rules! is_func { ($name:ident) => ( fn $name(&self) -> bool { if let Some(id) = self.id() { id.$name() } else { false } } ) } impl AttributeType for Attribute { is_func!(is_presentation); is_func!(is_inheritable); is_func!(is_animation_event); is_func!(is_graphical_event); is_func!(is_document_event); is_func!(is_conditional_processing); is_func!(is_core); is_func!(is_fill); is_func!(is_stroke); } impl AttributeType for AttributeId { fn is_presentation(&self) -> bool { match *self { AttributeId::AlignmentBaseline | AttributeId::BaselineShift | AttributeId::Clip | AttributeId::ClipPath | AttributeId::ClipRule | AttributeId::Color | AttributeId::ColorInterpolation | AttributeId::ColorInterpolationFilters | AttributeId::ColorProfile | AttributeId::ColorRendering | AttributeId::Cursor | AttributeId::Direction | AttributeId::Display | AttributeId::DominantBaseline | AttributeId::EnableBackground | AttributeId::Fill | AttributeId::FillOpacity | AttributeId::FillRule | AttributeId::Filter | AttributeId::FloodColor | AttributeId::FloodOpacity | AttributeId::Font | AttributeId::FontFamily | AttributeId::FontSize | AttributeId::FontSizeAdjust | AttributeId::FontStretch | AttributeId::FontStyle | AttributeId::FontVariant | AttributeId::FontWeight | AttributeId::GlyphOrientationHorizontal | AttributeId::GlyphOrientationVertical | AttributeId::ImageRendering | AttributeId::Kerning | AttributeId::LetterSpacing | AttributeId::LightingColor | AttributeId::Marker | AttributeId::MarkerEnd | AttributeId::MarkerMid | AttributeId::MarkerStart | AttributeId::Mask | AttributeId::Opacity | AttributeId::Overflow | AttributeId::PointerEvents | AttributeId::ShapeRendering | AttributeId::StopColor | AttributeId::StopOpacity | AttributeId::Stroke | AttributeId::StrokeDasharray | AttributeId::StrokeDashoffset | AttributeId::StrokeLinecap | AttributeId::StrokeLinejoin | AttributeId::StrokeMiterlimit | AttributeId::StrokeOpacity | AttributeId::StrokeWidth | AttributeId::TextAnchor | AttributeId::TextDecoration | AttributeId::TextRendering | AttributeId::UnicodeBidi | AttributeId::Visibility | AttributeId::WordSpacing | AttributeId::WritingMode => true, _ => false, } } fn is_inheritable(&self) -> bool { if self.is_presentation() { !is_non_inheritable(*self) } else { false } } fn is_animation_event(&self) -> bool { match *self { AttributeId::Onbegin | AttributeId::Onend | AttributeId::Onload | AttributeId::Onrepeat => true, _ => false, } } fn is_graphical_event(&self) -> bool { match *self { AttributeId::Onactivate | AttributeId::Onclick | AttributeId::Onfocusin | AttributeId::Onfocusout | AttributeId::Onload | AttributeId::Onmousedown | AttributeId::Onmousemove | AttributeId::Onmouseout | AttributeId::Onmouseover | AttributeId::Onmouseup => true, _ => false, } } fn is_document_event(&self) -> bool { match *self { AttributeId::Onabort | AttributeId::Onerror | AttributeId::Onresize | AttributeId::Onscroll | AttributeId::Onunload | AttributeId::Onzoom => true, _ => false, } } fn is_conditional_processing(&self) -> bool { match *self { AttributeId::RequiredExtensions | AttributeId::RequiredFeatures | AttributeId::SystemLanguage => true, _ => false, } } fn is_core(&self) -> bool { match *self { AttributeId::Base | AttributeId::Lang | AttributeId::Space => true, _ => false, } } fn is_fill(&self) -> bool { match *self { AttributeId::Fill | AttributeId::FillOpacity | AttributeId::FillRule => true, _ => false, } } fn is_stroke(&self) -> bool { match *self { AttributeId::Stroke | AttributeId::StrokeDasharray | AttributeId::StrokeDashoffset | AttributeId::StrokeLinecap | AttributeId::StrokeLinejoin | AttributeId::StrokeMiterlimit | AttributeId::StrokeOpacity | AttributeId::StrokeWidth => true, _ => false, } } } // NOTE: `visibility` is marked as inheritable here: https://www.w3.org/TR/SVG/propidx.html, // but here https://www.w3.org/TR/SVG/painting.html#VisibilityControl // we have "Note that `visibility` is not an inheritable property." // And here https://www.w3.org/TR/2008/REC-CSS2-20080411/visufx.html#propdef-visibility // Inherited: no // And according to webkit, it's really non-inheritable. fn is_non_inheritable(id: AttributeId) -> bool { match id { AttributeId::AlignmentBaseline | AttributeId::BaselineShift | AttributeId::Clip | AttributeId::ClipPath | AttributeId::Display | AttributeId::DominantBaseline | AttributeId::EnableBackground | AttributeId::Filter | AttributeId::FloodColor | AttributeId::FloodOpacity | AttributeId::LightingColor | AttributeId::Mask | AttributeId::Opacity | AttributeId::Overflow | AttributeId::StopColor | AttributeId::StopOpacity | AttributeId::TextDecoration | AttributeId::UnicodeBidi | AttributeId::Visibility => true, _ => false } } svgdom-0.18.0/src/attribute_value.rs010064400017500001750000000240041351403653400156600ustar0000000000000000use std::fmt; use crate::{ Angle, AspectRatio, AttributeId, Color, Length, LengthList, LengthUnit, Node, NumberList, PaintFallback, Path, Points, Transform, ValueWriteOptions, ViewBox, WriteBuffer, }; // TODO: custom debug /// Value of the SVG attribute. #[derive(Clone, PartialEq, Debug)] #[allow(missing_docs)] pub enum AttributeValue { None, Inherit, CurrentColor, AspectRatio(AspectRatio), Color(Color), /// FuncIRI FuncLink(Node), Paint(Node, Option), Length(Length), LengthList(LengthList), Angle(Angle), /// IRI Link(Node), Number(f64), NumberList(NumberList), Path(Path), Points(Points), Transform(Transform), ViewBox(ViewBox), String(String), } macro_rules! impl_from { ($vt:ty, $vtn:ident) => ( impl From<$vt> for AttributeValue { fn from(value: $vt) -> Self { AttributeValue::$vtn(value) } } ) } impl_from!(AspectRatio, AspectRatio); impl_from!(Color, Color); impl_from!(Length, Length); impl_from!(LengthList, LengthList); impl_from!(Angle, Angle); impl_from!(f64, Number); impl_from!(NumberList, NumberList); impl_from!(Path, Path); impl_from!(Points, Points); impl_from!(String, String); impl_from!(Transform, Transform); impl_from!(ViewBox, ViewBox); // TODO: bad, hidden allocation impl<'a> From<&'a str> for AttributeValue { fn from(value: &str) -> Self { AttributeValue::String(value.to_owned()) } } impl From for AttributeValue { fn from(value: i32) -> Self { AttributeValue::Number(f64::from(value)) } } impl From<(i32, LengthUnit)> for AttributeValue { fn from(value: (i32, LengthUnit)) -> Self { AttributeValue::Length(Length::new(f64::from(value.0), value.1)) } } impl From<(f64, LengthUnit)> for AttributeValue { fn from(value: (f64, LengthUnit)) -> Self { AttributeValue::Length(Length::new(value.0, value.1)) } } impl From for AttributeValue { fn from(value: PaintFallback) -> Self { match value { PaintFallback::None => AttributeValue::None, PaintFallback::CurrentColor => AttributeValue::CurrentColor, PaintFallback::Color(c) => AttributeValue::Color(c), } } } // TODO: fix docs macro_rules! impl_is_type { ($name:ident, $t:ident) => ( #[allow(missing_docs)] pub fn $name(&self) -> bool { match *self { AttributeValue::$t(..) => true, _ => false, } } ) } macro_rules! impl_is_type_without_value { ($name:ident, $t:ident) => ( #[allow(missing_docs)] pub fn $name(&self) -> bool { match *self { AttributeValue::$t => true, _ => false, } } ) } impl AttributeValue { impl_is_type_without_value!(is_none, None); impl_is_type_without_value!(is_inherit, Inherit); impl_is_type_without_value!(is_current_color, CurrentColor); impl_is_type!(is_aspect_ratio, AspectRatio); impl_is_type!(is_color, Color); impl_is_type!(is_length, Length); impl_is_type!(is_length_list, LengthList); impl_is_type!(is_angle, Angle); impl_is_type!(is_link, Link); impl_is_type!(is_func_link, FuncLink); impl_is_type!(is_paint, Paint); impl_is_type!(is_number, Number); impl_is_type!(is_number_list, NumberList); impl_is_type!(is_path, Path); impl_is_type!(is_points, Points); impl_is_type!(is_string, String); impl_is_type!(is_transform, Transform); impl_is_type!(is_viewbox, ViewBox); /// Checks that the current attribute value contains a `Node`. /// /// E.g. `Link`, `FuncLink` and `Paint`. pub fn is_link_container(&self) -> bool { match *self { AttributeValue::Link(_) | AttributeValue::FuncLink(_) | AttributeValue::Paint(_, _) => true, _ => false, } } /// Constructs a new attribute value with a default value, if it's known. pub fn default_value(id: AttributeId) -> Option { macro_rules! some { ($expr:expr) => (Some(AttributeValue::from($expr))) } match id { AttributeId::AlignmentBaseline | AttributeId::Clip | AttributeId::ColorProfile | AttributeId::ColorRendering | AttributeId::Cursor | AttributeId::DominantBaseline | AttributeId::GlyphOrientationVertical | AttributeId::ImageRendering | AttributeId::Kerning | AttributeId::ShapeRendering | AttributeId::TextRendering => some!("auto"), AttributeId::ClipPath | AttributeId::Filter | AttributeId::FontSizeAdjust | AttributeId::Marker | AttributeId::MarkerEnd | AttributeId::MarkerMid | AttributeId::MarkerStart | AttributeId::Mask | AttributeId::Stroke | AttributeId::StrokeDasharray | AttributeId::TextDecoration => Some(AttributeValue::None), AttributeId::FontStretch | AttributeId::FontStyle | AttributeId::FontVariant | AttributeId::FontWeight | AttributeId::LetterSpacing | AttributeId::UnicodeBidi | AttributeId::WordSpacing => some!("normal"), AttributeId::Fill | AttributeId::FloodColor | AttributeId::StopColor => some!(Color::black()), AttributeId::FillOpacity | AttributeId::FloodOpacity | AttributeId::Opacity | AttributeId::StopOpacity | AttributeId::StrokeOpacity => some!(1.0), AttributeId::ClipRule | AttributeId::FillRule => some!("nonzero"), AttributeId::BaselineShift => some!("baseline"), AttributeId::ColorInterpolation => some!("sRGB"), AttributeId::ColorInterpolationFilters => some!("linearRGB"), AttributeId::Direction => some!("ltr"), AttributeId::Display => some!("inline"), AttributeId::EnableBackground => some!("accumulate"), AttributeId::FontSize => some!("medium"), AttributeId::GlyphOrientationHorizontal => some!("0deg"), AttributeId::LightingColor => some!(Color::white()), AttributeId::StrokeDashoffset => some!((0.0, LengthUnit::None)), AttributeId::StrokeLinecap => some!("butt"), AttributeId::StrokeLinejoin => some!("miter"), AttributeId::StrokeMiterlimit => some!((4.0, LengthUnit::None)), AttributeId::StrokeWidth => some!((1.0, LengthUnit::None)), AttributeId::TextAnchor => some!("start"), AttributeId::Visibility => some!("visible"), AttributeId::WritingMode => some!("lr-tb"), _ => None, } } } impl WriteBuffer for AttributeValue { fn write_buf_opt(&self, opt: &ValueWriteOptions, buf: &mut Vec) { match *self { AttributeValue::None => { buf.extend_from_slice(b"none"); } AttributeValue::Inherit => { buf.extend_from_slice(b"inherit"); } AttributeValue::CurrentColor => { buf.extend_from_slice(b"currentColor"); } AttributeValue::String(ref s) => { buf.extend_from_slice(s.as_bytes()); } AttributeValue::Number(ref n) => { n.write_buf_opt(opt, buf); } AttributeValue::NumberList(ref list) => { list.write_buf_opt(opt, buf); } AttributeValue::Length(ref l) => { l.write_buf_opt(opt, buf); } AttributeValue::LengthList(ref list) => { list.write_buf_opt(opt, buf); } AttributeValue::Angle(ref a) => { a.write_buf_opt(opt, buf); } AttributeValue::Transform(ref t) => { t.write_buf_opt(opt, buf); } AttributeValue::Path(ref p) => { p.write_buf_opt(opt, buf); } AttributeValue::Points(ref p) => { p.write_buf_opt(opt, buf); } AttributeValue::Link(ref n) => { buf.push(b'#'); buf.extend_from_slice(n.id().as_bytes()); } AttributeValue::FuncLink(ref n) => { buf.extend_from_slice(b"url(#"); buf.extend_from_slice(n.id().as_bytes()); buf.push(b')'); } AttributeValue::Paint(ref n, ref fallback) => { buf.extend_from_slice(b"url(#"); buf.extend_from_slice(n.id().as_bytes()); buf.push(b')'); if let Some(fallback) = *fallback { buf.push(b' '); match fallback { PaintFallback::None => buf.extend_from_slice(b"none"), PaintFallback::CurrentColor => buf.extend_from_slice(b"currentColor"), PaintFallback::Color(ref c) => c.write_buf_opt(opt, buf), } } } AttributeValue::Color(ref c) => { c.write_buf_opt(opt, buf); } AttributeValue::ViewBox(vb) => { vb.write_buf_opt(opt, buf); } AttributeValue::AspectRatio(ratio) => { ratio.write_buf_opt(opt, buf); } } } } impl fmt::Display for AttributeValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.with_write_opt(&ValueWriteOptions::default())) } } svgdom-0.18.0/src/attributes.rs010064400017500001750000000135501351362772200146570ustar0000000000000000use std::fmt; use std::str; use std::mem; use std::iter::FilterMap; use std::slice::{Iter, IterMut}; use crate::{ Attribute, AttributeId, AttributeQNameRef, AttributeValue, QName, }; /// An iterator over SVG attributes. pub trait FilterSvgAttrs: Iterator { /// Filters SVG attributes. fn svg<'a>(self) -> FilterMap Option<(AttributeId, &Attribute)>> where Self: Iterator + Sized { fn is_svg(attr: &Attribute) -> Option<(AttributeId, &Attribute)> { if let QName::Id(id) = attr.name { return Some((id, attr)); } None } self.filter_map(is_svg) } } impl<'a, I: Iterator> FilterSvgAttrs for I {} /// An iterator over SVG attributes. pub trait FilterSvgAttrsMut: Iterator { /// Filters SVG attributes. fn svg<'a>(self) -> FilterMap Option<(AttributeId, &mut Attribute)>> where Self: Iterator + Sized { fn is_svg(attr: &mut Attribute) -> Option<(AttributeId, &mut Attribute)> { if let QName::Id(id) = attr.name { return Some((id, attr)); } None } self.filter_map(is_svg) } } impl<'a, I: Iterator> FilterSvgAttrsMut for I {} /// An attributes list. pub struct Attributes(Vec); impl Attributes { /// Constructs a new attribute. #[inline] pub(crate) fn new() -> Attributes { Attributes(Vec::new()) } /// Returns an optional reference to [`Attribute`]. /// /// [`Attribute`]: struct.Attribute.html #[inline] pub fn get<'a, N>(&self, name: N) -> Option<&Attribute> where AttributeQNameRef<'a>: From { let name = AttributeQNameRef::from(name); for v in &self.0 { if v.name.as_ref() == name { return Some(v); } } None } /// Returns an optional mutable reference to [`Attribute`]. /// /// [`Attribute`]: struct.Attribute.html #[inline] pub fn get_mut<'a, N>(&mut self, name: N) -> Option<&mut Attribute> where AttributeQNameRef<'a>: From { let name = AttributeQNameRef::from(name); for v in &mut self.0 { if v.name.as_ref() == name { return Some(v); } } None } /// Returns an optional reference to [`AttributeValue`]. /// /// [`AttributeValue`]: enum.AttributeValue.html #[inline] pub fn get_value<'a, N>(&self, name: N) -> Option<&AttributeValue> where AttributeQNameRef<'a>: From { let name = AttributeQNameRef::from(name); for v in &self.0 { if v.name.as_ref() == name { return Some(&v.value); } } None } /// Returns an optional mutable reference to [`AttributeValue`]. /// /// [`AttributeValue`]: enum.AttributeValue.html #[inline] pub fn get_value_mut<'a, N>(&mut self, name: N) -> Option<&mut AttributeValue> where AttributeQNameRef<'a>: From { let name = AttributeQNameRef::from(name); for v in &mut self.0 { if v.name.as_ref() == name { return Some(&mut v.value); } } None } /// Inserts a new link attribute. pub(crate) fn insert(&mut self, attr: Attribute) { // Increase capacity on first insert. if self.0.capacity() == 0 { self.0.reserve(16); } let idx = self.0.iter().position(|x| x.name == attr.name); match idx { // We use braces to discard return value. Some(i) => { mem::replace(&mut self.0[i], attr); } None => self.0.push(attr), } } /// Removes an existing attribute. pub(crate) fn remove<'a, N>(&mut self, name: N) where AttributeQNameRef<'a>: From { let name = AttributeQNameRef::from(name); let idx = self.0.iter().position(|x| x.name.as_ref() == name); if let Some(i) = idx { self.0.remove(i); } } /// Returns `true` if the container contains an attribute with such name. #[inline] pub fn contains<'a, N>(&self, name: N) -> bool where AttributeQNameRef<'a>: From { let name = AttributeQNameRef::from(name); self.0.iter().any(|a| a.name.as_ref() == name) } /// Returns count of the attributes. #[inline] pub fn len(&self) -> usize { self.0.len() } /// Returns `true` if attributes is empty. #[inline] pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Returns an iterator. #[inline] pub fn iter(&self) -> Iter { self.0.iter() } /// Returns a mutable iterator. #[inline] pub fn iter_mut(&mut self) -> IterMut { self.0.iter_mut() } /// Clears the attributes list, removing all values. pub(crate) fn clear(&mut self) { self.0.clear(); } } impl IntoIterator for Attributes { type Item = Attribute; type IntoIter = ::std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl fmt::Debug for Attributes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { return write!(f, "Attributes({})", self); } } impl fmt::Display for Attributes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use std::io::Write; if self.is_empty() { return Ok(()); } let mut out = Vec::with_capacity(256); for attr in self.iter() { out.write_fmt(format_args!("{}", attr)).unwrap(); out.push(b' '); } out.pop(); write!(f, "{}", str::from_utf8(&out).unwrap()) } } svgdom-0.18.0/src/document.rs010064400017500001750000000267551352165564400143250ustar0000000000000000use std::fmt; use log::warn; use slab::Slab; use crate::parser::parse_svg; use crate::writer; use crate::{ AttributeQName, Attributes, AttributeValue, ElementId, FilterSvg, FilterSvgAttrs, Node, NodeData, NodeType, ParserError, QName, QNameRef, TagNameRef, WriteOptions, }; /// Container of [`Node`]s. /// /// Structure: /// /// - [`Document`] /// - root [`Node`] /// - user defined [`Node`] /// - [`TagName`] /// - [`Attributes`] /// - unique id /// - user defined [`Node`] /// - ... /// /// The [`Document`] itself is just a container of [`Node`]s. /// You can create new [`Node`]s only through the [`Document`]. /// Parsing and generating of the SVG data also done through it. /// /// The [`Node`] represents any kind of an XML node. /// It can be an element, a comment, a text, etc. There are no different structs for each type. /// /// The [`TagName`] represents a tag name of the element node. It's an enum of /// [`ElementId`] and `String` types. The [`ElementId`] contains all possible /// SVG element names and `String` used for non-SVG elements. Such separation used for /// performance reasons. /// /// The [`Attributes`] container wraps a `Vec` of [`Attribute`]'s. /// /// At last, the `id` attribute is stored as a separate value and not as part of the [`Attributes`]. /// /// [`Attribute`]: struct.Attribute.html /// [`Attributes`]: struct.Attributes.html /// [`Document`]: struct.Document.html /// [`ElementId`]: enum.ElementId.html /// [`Node`]: type.Node.html /// [`TagName`]: type.TagName.html pub struct Document { root: Node, storage: Slab, } impl Document { /// Constructs a new `Document`. pub fn new() -> Document { let mut storage = Slab::new(); let mut root = Node::new(NodeData { storage_key: None, node_type: NodeType::Root, tag_name: QName::Name(String::new()), id: String::new(), attributes: Attributes::new(), linked_nodes: Vec::new(), text: String::new(), }); let key = storage.insert(root.clone()); root.borrow_mut().storage_key = Some(key); Document { root, storage, } } /// Constructs a new `Document` from the text. /// /// [`ParseOptions`]: struct.ParseOptions.html /// /// **Note:** only SVG elements and attributes will be parsed. pub fn from_str(text: &str) -> Result { parse_svg(text) } /// Writes a `Document` content to a string. pub fn to_string_with_opt(&self, opt: &WriteOptions) -> String { writer::write_dom(self, opt) } /// Constructs a new [`Node`] with [`NodeType`]::Element type. /// /// Constructed node do belong to this document, but not added to it tree structure. /// /// # Panics /// /// Panics if a string tag name is empty. /// /// [`Node`]: type.Node.html /// [`NodeType`]: enum.NodeType.html pub fn create_element<'a, T>(&mut self, tag_name: T) -> Node where TagNameRef<'a>: From, T: Copy { let tn = QNameRef::from(tag_name); if let QNameRef::Name(name) = tn { if name.is_empty() { panic!("supplied tag name is empty"); } } let mut node = Node::new(NodeData { storage_key: None, node_type: NodeType::Element, tag_name: QNameRef::from(tag_name).into(), id: String::new(), attributes: Attributes::new(), linked_nodes: Vec::new(), text: String::new(), }); let key = self.storage.insert(node.clone()); node.borrow_mut().storage_key = Some(key); node } // TODO: we can't have continuous text nodes. // TODO: doc should have only one declaration /// Constructs a new [`Node`] using the supplied [`NodeType`]. /// /// Constructed node do belong to this document, but not added to it tree structure. /// /// This method should be used for any non-element nodes. /// /// [`Node`]: type.Node.html /// [`NodeType`]: enum.NodeType.html pub fn create_node>(&mut self, node_type: NodeType, text: S) -> Node { assert!(node_type != NodeType::Element && node_type != NodeType::Root); let mut node = Node::new(NodeData { storage_key: None, node_type, tag_name: QName::Name(String::new()), id: String::new(), attributes: Attributes::new(), linked_nodes: Vec::new(), text: text.into(), }); let key = self.storage.insert(node.clone()); node.borrow_mut().storage_key = Some(key); node } /// Returns the root [`Node`]. /// /// [`Node`]: type.Node.html pub fn root(&self) -> Node { self.root.clone() } /// Returns the first child with `svg` tag name of the root [`Node`]. /// /// In most of the cases result of this method and `first_element_child()` will be the same, /// but an additional check may be helpful. /// /// # Panics /// /// Panics if the root node is currently mutability borrowed. /// /// # Examples /// ``` /// use svgdom::{Document, ElementId}; /// /// let doc = Document::from_str( /// "").unwrap(); /// /// assert_eq!(doc.svg_element().unwrap().has_tag_name(ElementId::Svg), true); /// ``` /// /// [`Node`]: type.Node.html pub fn svg_element(&self) -> Option { for (id, n) in self.root().children().svg() { if id == ElementId::Svg { return Some(n.clone()); } } None } /// Removes this node and all it children from the tree. /// /// Same as `detach()`, but also removes all linked attributes from the tree. /// /// # Panics /// /// Panics if the node or one of its adjoining nodes or any children node is currently borrowed. /// /// # Examples /// ``` /// use svgdom::{Document, ElementId, AttributeId}; /// /// let mut doc = Document::from_str( /// " /// /// /// ").unwrap(); /// /// let mut rect_elem = doc.root().descendants().filter(|n| *n.id() == "rect1").next().unwrap(); /// let use_elem = doc.root().descendants().filter(|n| n.has_tag_name(ElementId::Use)).next().unwrap(); /// /// assert_eq!(use_elem.has_attribute(AttributeId::Href), true); /// /// // The 'remove' method will remove 'rect' element and all it's children. /// // Also it will remove all links to this element and it's children, /// // so 'use' element will no longer have the 'xlink:href' attribute. /// doc.remove_node(rect_elem); /// /// assert_eq!(use_elem.has_attribute(AttributeId::Href), false); /// ``` pub fn remove_node(&mut self, node: Node) { let mut ids = Vec::with_capacity(16); self._remove(node.clone(), &mut ids); } fn _remove(&mut self, mut node: Node, ids: &mut Vec) { ids.clear(); for (_, attr) in node.attributes().iter().svg() { match attr.value { AttributeValue::Link(_) | AttributeValue::FuncLink(_) | AttributeValue::Paint(_, _) => { ids.push(attr.name.clone()) } _ => {} } } for name in ids.iter() { node.remove_attribute(name.as_ref()); } // remove all attributes that linked to this node let linked_nodes = node.linked_nodes().clone(); for mut linked in linked_nodes { ids.clear(); for (_, attr) in linked.attributes().iter().svg() { match attr.value { AttributeValue::Link(ref link) | AttributeValue::FuncLink(ref link) | AttributeValue::Paint(ref link, _) => { if *link == node { ids.push(attr.name.clone()) } } _ => {} } } for name in ids.iter() { linked.remove_attribute(name.as_ref()); } } // repeat for children for child in node.children() { self._remove(child, ids); } node.detach(); let key = node.borrow_mut().storage_key.take(); if let Some(key) = key { self.storage.remove(key); } else { warn!("Node was already removed.") } } // TODO: maybe rename to retain to match Attributes::retain /// Removes only the children nodes specified by the predicate. /// /// Uses [remove()](#method.remove), not [detach()](#method.detach) internally. /// /// The `root` node will be ignored. pub fn drain

(&mut self, root: Node, f: P) -> usize where P: Fn(&Node) -> bool { let mut count = 0; self._drain(root, &f, &mut count); count } fn _drain

(&mut self, parent: Node, f: &P, count: &mut usize) where P: Fn(&Node) -> bool { let mut node = parent.first_child(); while let Some(n) = node { if f(&n) { node = n.next_sibling(); self.remove_node(n); *count += 1; } else { if n.has_children() { self._drain(n.clone(), f, count); } node = n.next_sibling(); } } } /// Returns a copy of a current node without children. /// /// All attributes except `id` will be copied, because `id` must be unique. pub fn copy_node(&mut self, node: Node) -> Node { match node.node_type() { NodeType::Element => { let mut elem = self.create_element(node.tag_name().as_ref()); for attr in node.attributes().iter() { elem.set_attribute(attr.clone()); } elem } _ => { self.create_node(node.node_type(), node.text().clone()) } } } /// Returns a deep copy of a current node with all it's children. /// /// All attributes except `id` will be copied, because `id` must be unique. pub fn copy_node_deep(&mut self, node: Node) -> Node { let mut root = self.copy_node(node.clone()); self._make_deep_copy(&mut root, &node); root } fn _make_deep_copy(&mut self, parent: &mut Node, node: &Node) { for child in node.children() { let mut new_node = self.copy_node(child.clone()); parent.append(new_node.clone()); if child.has_children() { self._make_deep_copy(&mut new_node, &child); } } } } impl Drop for Document { fn drop(&mut self) { for (_, node) in self.storage.iter_mut() { node.attributes_mut().clear(); node.linked_nodes_mut().clear(); node.detach(); } } } impl fmt::Display for Document { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let data = writer::write_dom(self, &WriteOptions::default()); write!(f, "{}", data) } } svgdom-0.18.0/src/element_type.rs010064400017500001750000000154001347344454100151600ustar0000000000000000use crate::{ ElementId, Node, }; /// This trait contains methods that check element's type according to the /// [SVG spec](https://www.w3.org/TR/SVG/intro.html#Definitions). /// /// Note that all methods works with `Node` type and will return `false` /// if node's type is not equal to `NodeType::Element`. /// /// # Panics /// /// All methods panics if the node is currently mutability borrowed. pub trait ElementType { /// Returns true if the current node is referenced. /// /// Referenced elements are elements that do not render by itself, /// rather defines rendering properties for other. /// /// List: `altGlyphDef`, `clipPath`, `cursor`, `filter`, `linearGradient`, `marker`, /// `mask`, `pattern`, `radialGradient` and `symbol`. /// /// Details: /// /// # Examples /// /// ``` /// use svgdom::{Document, ElementType}; /// /// let doc = Document::from_str( /// "").unwrap(); /// let mut iter = doc.root().descendants(); /// assert_eq!(iter.next().unwrap().is_referenced(), false); // root /// assert_eq!(iter.next().unwrap().is_referenced(), false); // svg /// assert_eq!(iter.next().unwrap().is_referenced(), true); // linearGradient /// ``` fn is_referenced(&self) -> bool; /// Returns true if the current node is a basic shape element. /// /// List: `rect`, `circle`, `ellipse`, `line`, `polyline`, `polygon`. /// /// Details: fn is_basic_shape(&self) -> bool; /// Returns true if the current node is a shape element. /// /// List: `path`, `rect`, `circle`, `ellipse`, `line`, `polyline` and `polygon`. /// /// Details: fn is_shape(&self) -> bool; /// Returns true if the current node is a container element. /// /// List: `a`, `defs`, `glyph`, `g`, `marker`, `mask`, `missing-glyph`, `pattern`, `svg`, /// `switch` and `symbol`. /// /// Details: fn is_container(&self) -> bool; /// Returns true if the current node is a text content element. /// /// List: `altGlyph`, `textPath`, `text`, `tref` and `tspan`. /// /// Details: fn is_text_content(&self) -> bool; /// Returns true if the current node is a text content child element. /// /// List: `altGlyph`, `textPath`, `tref` and `tspan`. /// /// Details: fn is_text_content_child(&self) -> bool; /// Returns true if the current node is a graphic element. /// /// List: `circle`, `ellipse`, `image`, `line`, `path`, `polygon`, `polyline`, `rect`, /// `text` and `use`. /// /// Details: fn is_graphic(&self) -> bool; /// Returns true if the current node is a gradient element. /// /// List: `linearGradient`, `radialGradient`. fn is_gradient(&self) -> bool; /// Returns true if the current node is a [paint server]. /// /// List: `linearGradient`, `radialGradient` and `pattern`. /// /// [paint server]: fn is_paint_server(&self) -> bool; /// Returns true if the current node is a [filter primitive]. /// /// List: `feBlend`, `feColorMatrix`, `feComponentTransfer`, `feComposite`, /// `feConvolveMatrix`, `feDiffuseLighting`, `feDisplacementMap`, `feFlood`, `feGaussianBlur`, /// `feImage`, `feMerge`, `feMorphology`, `feOffset`, `feSpecularLighting`, /// `feTile` and `feTurbulence`. /// /// [filter primitive]: fn is_filter_primitive(&self) -> bool; } macro_rules! is_func { ($name:ident, $($pattern:tt)+) => ( fn $name(&self) -> bool { if let Some(id) = self.tag_id() { match id { $($pattern)+ => true, _ => false } } else { false } } ) } impl ElementType for Node { is_func!(is_referenced, ElementId::AltGlyphDef | ElementId::ClipPath | ElementId::Cursor | ElementId::Filter | ElementId::LinearGradient | ElementId::Marker | ElementId::Mask | ElementId::Pattern | ElementId::RadialGradient | ElementId::Symbol); is_func!(is_basic_shape, ElementId::Rect | ElementId::Circle | ElementId::Ellipse | ElementId::Line | ElementId::Polyline | ElementId::Polygon); is_func!(is_shape, ElementId::Circle | ElementId::Ellipse | ElementId::Line | ElementId::Path | ElementId::Polygon | ElementId::Polyline | ElementId::Rect); is_func!(is_container, ElementId::A | ElementId::Defs | ElementId::Glyph | ElementId::G | ElementId::Marker | ElementId::Mask | ElementId::MissingGlyph | ElementId::Pattern | ElementId::Svg | ElementId::Switch | ElementId::Symbol); is_func!(is_text_content, ElementId::AltGlyph | ElementId::TextPath | ElementId::Text | ElementId::Tref | ElementId::Tspan); is_func!(is_text_content_child, ElementId::AltGlyph | ElementId::TextPath | ElementId::Tref | ElementId::Tspan); is_func!(is_graphic, ElementId::Circle | ElementId::Ellipse | ElementId::Image | ElementId::Line | ElementId::Path | ElementId::Polygon | ElementId::Polyline | ElementId::Rect | ElementId::Text | ElementId::Use); is_func!(is_gradient, ElementId::LinearGradient | ElementId::RadialGradient); is_func!(is_paint_server, ElementId::LinearGradient | ElementId::RadialGradient | ElementId::Pattern); is_func!(is_filter_primitive, ElementId::FeBlend | ElementId::FeColorMatrix | ElementId::FeComponentTransfer | ElementId::FeComposite | ElementId::FeConvolveMatrix | ElementId::FeDiffuseLighting | ElementId::FeDisplacementMap | ElementId::FeFlood | ElementId::FeGaussianBlur | ElementId::FeImage | ElementId::FeMerge | ElementId::FeMorphology | ElementId::FeOffset | ElementId::FeSpecularLighting | ElementId::FeTile | ElementId::FeTurbulence); } svgdom-0.18.0/src/error.rs010064400017500001750000000047711347352443400136300ustar0000000000000000use std::error; use std::fmt; use roxmltree::{self, TextPos}; /// SVG DOM errors. #[derive(Debug)] pub enum Error { /// If you want to use referenced element inside link attribute, /// such element must have a non-empty ID. ElementMustHaveAnId, /// Linked nodes can't reference each other or itself. /// /// # Examples /// /// ```text /// /// /// ``` /// /// or /// /// ```text /// /// ``` ElementCrosslink, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::ElementMustHaveAnId => { write!(f, "the element must have an id") } Error::ElementCrosslink => { write!(f, "element crosslink") } } } } impl error::Error for Error { fn description(&self) -> &str { "an SVG error" } } /// SVG parsing errors. #[derive(Debug)] pub enum ParserError { /// Parsed document must have an `svg` element. NoSvgElement, /// *svgdom* didn't support most of the CSS2 spec. UnsupportedCSS(TextPos), /// A DOM API error. DomError(Error), /// An invalid attribute value. InvalidAttributeValue(TextPos), /// A `roxmltree` error. RoXmlError(roxmltree::Error), } impl fmt::Display for ParserError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ParserError::NoSvgElement => { write!(f, "the document does not have an SVG element") } ParserError::UnsupportedCSS(pos) => { write!(f, "unsupported CSS at {}", pos) } ParserError::InvalidAttributeValue(pos) => { write!(f, "invalid attribute value at {}", pos) } ParserError::DomError(ref e) => { write!(f, "{}", e) } ParserError::RoXmlError(ref e) => { write!(f, "{}", e) } } } } impl error::Error for ParserError { fn description(&self) -> &str { "an SVG parsing error" } } impl From for ParserError { fn from(value: Error) -> Self { ParserError::DomError(value) } } impl From for ParserError { fn from(value: roxmltree::Error) -> Self { ParserError::RoXmlError(value) } } svgdom-0.18.0/src/lib.rs010064400017500001750000000144751353151130500132330ustar0000000000000000/*! *svgdom* is an [SVG Full 1.1](https://www.w3.org/TR/SVG/) processing library, which allows you to parse, manipulate, generate and write an SVG content. ## Deprecation This library was an attempt to create a generic SVG DOM which can be used by various applications. But it the end it turned out that it's easier to use [roxmltree](https://github.com/RazrFalcon/roxmltree) + [svgtypes](https://github.com/RazrFalcon/svgtypes) to extract only the data you need. There are two main problems with `svgdom`: 1. You can't make a nice API with a Vec-based tree and you can't have a safe API with an Rc-tree. The current implementation uses so-called Rc-tree, which provides a nice API, but all the checks are done in the runtime, so you can get a panic quite easily. It's also hard/verbose to make immutable nodes. You essentially need two types of nodes: one for immutable and one for mutable "references". A Vec-based tree would not have such problems, but you can't implement the simplest operations with it, like copying an attribute from one node to another since you have to have a mutable and an immutable references for this. And Rust forbids this. So you need some sort of generational indexes and so on. This solution is complicated in its own way. Performance is also in question, since inserting/removing an object in the middle of a Vec is expensive. 2. The SVG parsing itself is pretty complex too. There are a lot of ways you can implement it. `svgdom` creates a custom Rc-tree where all the attributes are stored as owned data. This requires a lot of allocations (usually unnecessary). The parsing/preprocessing algorithm itself can be found in [docs/preprocessor.md](docs/preprocessor.md) The problem with it is that you can't tweak it. And in many cases, it produces results that you do not need or do not expect. `svgdom` was originally used by [svgcleaner](https://github.com/RazrFalcon/svgcleaner) and [resvg](https://github.com/RazrFalcon/resvg) and both of these projects are no longer using it. ## Purpose *svgdom* is designed to simplify generic SVG processing and manipulations. Unfortunately, an SVG is very complex format (PDF spec is 826 pages long), with lots of features and implementing all of them will lead to an enormous library. That's why *svgdom* supports only a static subset of an SVG. No scripts, external resources and complex CSS styling. Parser will convert as much as possible data to a simple doc->elements->attributes structure. For example, the `fill` parameter of an element can be set: as an element's attribute, as part of a `style` attribute, inside a `style` element as CSS2, inside an `ENTITY`, using a JS code and probably with lots of other methods. Not to mention, that the `fill` attribute supports 4 different types of data. With `svgdom` you can just use `node.has_attribute(AttributeId::Fill)` and don't worry where this attribute was defined in the original file. Same goes for transforms, paths and other SVG types. The main downside of this approach is that you can't save an original formatting and some data. See the [preprocessor](https://github.com/RazrFalcon/svgdom/blob/master/docs/preprocessor.md) doc for details. ## Benefits - The element link(IRI, FuncIRI) is not just a text, but an actual link to another node. - At any time you can check which elements linked to the specific element. See `Node`'s doc for details. - Support for many SVG specific data types like paths, transforms, IRI's, styles, etc. Thanks to [svgtypes](https://github.com/RazrFalcon/svgtypes). - A complete support of text nodes: XML escaping, `xml:space`. - Fine-grained control over the SVG output. ## Limitations - Only SVG elements and attributes will be parsed. - Attribute values, CDATA with CSS, DOCTYPE, text data and whitespaces will not be preserved. - UTF-8 only. - Only most popular attributes are parsed, other stored as strings. - No compressed SVG (.svgz). You should decompress it by yourself. - CSS support is minimal. - SVG 1.1 Full only (no 2.0 Draft, Basic, Tiny subsets). ## Differences between svgdom and SVG spec - Library follows SVG spec in the data parsing, writing, but not in the tree structure. - Everything is a `Node`. There are no separated `ElementNode`, `TextNode`, etc. You still have all the data, but not in the specific *struct's*. You can check the node type via `Node::node_type()`. */ #![doc(html_root_url = "https://docs.rs/svgdom/0.18.0")] #![forbid(unsafe_code)] #![warn(missing_docs)] mod attribute; mod document; mod node; mod tree; mod element_type; mod error; mod name; mod names; mod parser; mod writer; mod attribute_type; mod attribute_value; mod attributes; pub use crate::attribute::*; pub use crate::attribute_type::AttributeType; pub use crate::attribute_value::AttributeValue; pub use crate::attributes::*; pub use crate::document::Document; pub use crate::element_type::ElementType; pub use crate::error::*; pub use crate::name::*; pub use crate::names::*; pub use crate::node::*; pub use crate::tree::iterator::*; pub use crate::writer::*; pub use svgtypes::{ Align, Angle, AngleUnit, AspectRatio, Color, FuzzyEq, FuzzyZero, Length, LengthList, LengthUnit, ListSeparator, NumberList, PaintFallback, Path, PathCommand, PathSegment, Points, Transform, ViewBox, WriteBuffer, WriteOptions as ValueWriteOptions, }; /// Type alias for `QNameRef`. pub type TagNameRef<'a> = QNameRef<'a, ElementId>; /// Type alias for `QName`. pub type TagName = QName; /// Type alias for `QName`. pub type AttributeQName = QName; /// Type alias for `QNameRef`. pub type AttributeQNameRef<'a> = QNameRef<'a, AttributeId>; /// List of supported node types. #[derive(Clone, Copy, PartialEq, Debug)] pub enum NodeType { /// The root node of the `Document`. /// /// Constructed with `Document`. Unavailable to the user. Root, /// An element node. /// /// Only an element can have attributes, ID and tag name. Element, /// A comment node. Comment, /// A text node. Text, } /// Node's data. pub struct NodeData { storage_key: Option, node_type: NodeType, tag_name: TagName, id: String, attributes: Attributes, linked_nodes: Vec, text: String, } svgdom-0.18.0/src/name.rs010064400017500001750000000051311351362772600134110ustar0000000000000000//! This module contains a `Name` wrapper which is used for element tag name and attribute name. use std::fmt; use crate::{ AttributeId, ElementId, }; /// A trait for SVG id's. pub trait SvgId: Copy + PartialEq { /// Converts ID into name. fn name(&self) -> &str; } impl SvgId for AttributeId { fn name(&self) -> &str { self.as_str() } } impl SvgId for ElementId { fn name(&self) -> &str { self.as_str() } } /// Qualified name. #[derive(Clone, PartialEq, Debug)] pub enum QName { /// For an SVG name. Id(T), /// For an unknown name. Name(String), } impl QName { /// Returns `QName` as `QNameRef`. pub fn as_ref(&self) -> QNameRef { match *self { QName::Id(id) => QNameRef::Id(id), QName::Name(ref name) => QNameRef::Name(name), } } /// Checks that this name has specified ID. pub fn has_id(&self, id: T) -> bool { match *self { QName::Id(id2) => id == id2, _ => false, } } } impl fmt::Display for QName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { QName::Id(AttributeId::Href) => write!(f, "xlink:href"), QName::Id(AttributeId::Space) => write!(f, "xml:space"), QName::Id(id) => write!(f, "{}", id.name()), QName::Name(ref name) => write!(f, "{}", name), } } } impl fmt::Display for QName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { QName::Id(id) => write!(f, "{}", id.name()), QName::Name(ref name) => write!(f, "{}", name), } } } /// Qualified name reference. #[derive(Clone, Copy, PartialEq, Debug)] pub enum QNameRef<'a, T: SvgId> { /// For an SVG name. Id(T), /// For an unknown name. Name(&'a str), } impl<'a, T: SvgId> QNameRef<'a, T> { /// Checks that this name has specified ID. pub fn has_id(&self, id: T) -> bool { match *self { QNameRef::Id(id2) => id == id2, _ => false, } } } impl<'a, T: SvgId> From for QNameRef<'a, T> { fn from(value: T) -> Self { QNameRef::Id(value.into()) } } impl<'a, T: SvgId> From<&'a str> for QNameRef<'a, T> { fn from(value: &'a str) -> Self { QNameRef::Name(value.into()) } } impl<'a, T: SvgId> From> for QName { fn from(value: QNameRef) -> Self { match value { QNameRef::Id(id) => QName::Id(id), QNameRef::Name(name) => QName::Name(name.into()), } } } svgdom-0.18.0/src/names.rs010064400017500001750000000622741352302114700135710ustar0000000000000000// This file is autogenerated. Do not edit it! // See ./codegen for details. use std::fmt; /// List of all SVG elements. #[derive(Copy,Clone,Eq,PartialEq,Ord,PartialOrd,Hash)] #[allow(missing_docs)] pub enum ElementId { A, AltGlyph, AltGlyphDef, AltGlyphItem, Animate, AnimateColor, AnimateMotion, AnimateTransform, Circle, ClipPath, ColorProfile, Cursor, Defs, Desc, Ellipse, FeBlend, FeColorMatrix, FeComponentTransfer, FeComposite, FeConvolveMatrix, FeDiffuseLighting, FeDisplacementMap, FeDistantLight, FeFlood, FeFuncA, FeFuncB, FeFuncG, FeFuncR, FeGaussianBlur, FeImage, FeMerge, FeMergeNode, FeMorphology, FeOffset, FePointLight, FeSpecularLighting, FeSpotLight, FeTile, FeTurbulence, Filter, Font, FontFace, FontFaceFormat, FontFaceName, FontFaceSrc, FontFaceUri, ForeignObject, G, Glyph, GlyphRef, Hkern, Image, Line, LinearGradient, Marker, Mask, Metadata, MissingGlyph, Mpath, Path, Pattern, Polygon, Polyline, RadialGradient, Rect, Script, Set, Stop, Style, Svg, Switch, Symbol, Text, TextPath, Title, Tref, Tspan, Use, View, Vkern } static ELEMENTS: Map = Map { key: 732231254413039614, disps: &[ (5, 43), (0, 2), (0, 41), (0, 0), (0, 16), (11, 5), (0, 12), (4, 18), (22, 32), (5, 9), (2, 56), (6, 13), (2, 24), (0, 21), (9, 52), (0, 3), ], entries: &[ ("font-face-uri", ElementId::FontFaceUri), ("feFuncR", ElementId::FeFuncR), ("text", ElementId::Text), ("title", ElementId::Title), ("feBlend", ElementId::FeBlend), ("missing-glyph", ElementId::MissingGlyph), ("animateColor", ElementId::AnimateColor), ("marker", ElementId::Marker), ("feMergeNode", ElementId::FeMergeNode), ("filter", ElementId::Filter), ("feOffset", ElementId::FeOffset), ("feSpecularLighting", ElementId::FeSpecularLighting), ("stop", ElementId::Stop), ("clipPath", ElementId::ClipPath), ("line", ElementId::Line), ("ellipse", ElementId::Ellipse), ("radialGradient", ElementId::RadialGradient), ("feConvolveMatrix", ElementId::FeConvolveMatrix), ("feMerge", ElementId::FeMerge), ("feDiffuseLighting", ElementId::FeDiffuseLighting), ("feFuncG", ElementId::FeFuncG), ("feMorphology", ElementId::FeMorphology), ("color-profile", ElementId::ColorProfile), ("use", ElementId::Use), ("animate", ElementId::Animate), ("polyline", ElementId::Polyline), ("hkern", ElementId::Hkern), ("svg", ElementId::Svg), ("feComposite", ElementId::FeComposite), ("rect", ElementId::Rect), ("font-face", ElementId::FontFace), ("a", ElementId::A), ("path", ElementId::Path), ("linearGradient", ElementId::LinearGradient), ("feComponentTransfer", ElementId::FeComponentTransfer), ("feTile", ElementId::FeTile), ("altGlyphDef", ElementId::AltGlyphDef), ("feFuncA", ElementId::FeFuncA), ("symbol", ElementId::Symbol), ("glyphRef", ElementId::GlyphRef), ("glyph", ElementId::Glyph), ("style", ElementId::Style), ("switch", ElementId::Switch), ("set", ElementId::Set), ("font-face-name", ElementId::FontFaceName), ("font-face-src", ElementId::FontFaceSrc), ("font-face-format", ElementId::FontFaceFormat), ("feFlood", ElementId::FeFlood), ("tref", ElementId::Tref), ("feFuncB", ElementId::FeFuncB), ("mpath", ElementId::Mpath), ("animateMotion", ElementId::AnimateMotion), ("defs", ElementId::Defs), ("feGaussianBlur", ElementId::FeGaussianBlur), ("view", ElementId::View), ("foreignObject", ElementId::ForeignObject), ("tspan", ElementId::Tspan), ("altGlyph", ElementId::AltGlyph), ("cursor", ElementId::Cursor), ("feDisplacementMap", ElementId::FeDisplacementMap), ("pattern", ElementId::Pattern), ("mask", ElementId::Mask), ("font", ElementId::Font), ("desc", ElementId::Desc), ("feImage", ElementId::FeImage), ("feColorMatrix", ElementId::FeColorMatrix), ("feTurbulence", ElementId::FeTurbulence), ("feDistantLight", ElementId::FeDistantLight), ("g", ElementId::G), ("fePointLight", ElementId::FePointLight), ("feSpotLight", ElementId::FeSpotLight), ("script", ElementId::Script), ("image", ElementId::Image), ("metadata", ElementId::Metadata), ("vkern", ElementId::Vkern), ("textPath", ElementId::TextPath), ("polygon", ElementId::Polygon), ("circle", ElementId::Circle), ("altGlyphItem", ElementId::AltGlyphItem), ("animateTransform", ElementId::AnimateTransform), ], }; impl ElementId { /// Parses `ElementId` from a string. pub fn from_str(text: &str) -> Option { ELEMENTS.get(text).cloned() } /// Returns an original string. pub fn as_str(&self) -> &'static str { ELEMENTS.key(self) } } impl fmt::Debug for ElementId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl fmt::Display for ElementId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_str()) } } /// List of all SVG attributes. #[derive(Copy,Clone,Eq,PartialEq,Ord,PartialOrd,Hash)] #[allow(missing_docs)] pub enum AttributeId { AccentHeight, Accumulate, Actuate, Additive, AlignmentBaseline, Alphabetic, Amplitude, ArabicForm, Arcrole, Ascent, AttributeName, AttributeType, Azimuth, Base, BaseFrequency, BaselineShift, BaseProfile, Bbox, Begin, Bias, By, CalcMode, CapHeight, Class, Clip, ClipPath, ClipRule, ClipPathUnits, Color, ColorInterpolation, ColorInterpolationFilters, ColorProfile, ColorRendering, ContentScriptType, ContentStyleType, Cursor, Cx, Cy, D, Descent, DiffuseConstant, Direction, Display, Divisor, DominantBaseline, Dur, Dx, Dy, EdgeMode, Elevation, EnableBackground, Encoding, End, Exponent, ExternalResourcesRequired, Fill, FillOpacity, FillRule, Filter, FilterRes, FilterUnits, FloodColor, FloodOpacity, Font, FontFamily, FontSize, FontSizeAdjust, FontStretch, FontStyle, FontVariant, FontWeight, Format, From, Fx, Fy, G1, G2, GlyphName, GlyphOrientationHorizontal, GlyphOrientationVertical, GlyphRef, GradientTransform, GradientUnits, Hanging, Height, HorizAdvX, HorizOriginX, HorizOriginY, Href, Id, Ideographic, ImageRendering, In, In2, Intercept, K, K1, K2, K3, K4, KernelMatrix, KernelUnitLength, Kerning, KeyPoints, KeySplines, KeyTimes, Lang, LengthAdjust, LetterSpacing, LightingColor, LimitingConeAngle, LineHeight, Local, Marker, MarkerEnd, MarkerMid, MarkerStart, MarkerHeight, MarkerUnits, MarkerWidth, Mask, MaskContentUnits, MaskUnits, Mathematical, Max, Media, Method, Min, Mode, Name, NumOctaves, Offset, Onabort, Onactivate, Onbegin, Onclick, Onend, Onerror, Onfocusin, Onfocusout, Onload, Onmousedown, Onmousemove, Onmouseout, Onmouseover, Onmouseup, Onrepeat, Onresize, Onscroll, Onunload, Onzoom, Opacity, Operator, Order, Orient, Orientation, Origin, Overflow, OverlinePosition, OverlineThickness, Panose1, Path, PathLength, PatternContentUnits, PatternTransform, PatternUnits, PointerEvents, Points, PointsAtX, PointsAtY, PointsAtZ, PreserveAlpha, PreserveAspectRatio, PrimitiveUnits, R, Radius, RefX, RefY, RenderingIntent, RepeatCount, RepeatDur, RequiredExtensions, RequiredFeatures, Restart, Result, Role, Rotate, Rx, Ry, Scale, Seed, ShapeRendering, Show, Slope, Space, Spacing, SpecularConstant, SpecularExponent, SpreadMethod, Standalone, StartOffset, StdDeviation, Stemh, Stemv, StitchTiles, StopColor, StopOpacity, StrikethroughPosition, StrikethroughThickness, String, Stroke, StrokeDasharray, StrokeDashoffset, StrokeLinecap, StrokeLinejoin, StrokeMiterlimit, StrokeOpacity, StrokeWidth, Style, SurfaceScale, SystemLanguage, TableValues, Target, TargetX, TargetY, TextAnchor, TextDecoration, TextRendering, TextLength, Title, To, Transform, Type, U1, U2, UnderlinePosition, UnderlineThickness, Unicode, UnicodeBidi, UnicodeRange, UnitsPerEm, VAlphabetic, VHanging, VIdeographic, VMathematical, Values, Version, VertAdvY, VertOriginX, VertOriginY, ViewBox, ViewTarget, Visibility, Width, Widths, WordSpacing, WritingMode, X, XHeight, X1, X2, XChannelSelector, Xlink, Xmlns, Y, Y1, Y2, YChannelSelector, Z, ZoomAndPan } static ATTRIBUTES: Map = Map { key: 732231254413039614, disps: &[ (0, 1), (1, 123), (0, 115), (0, 2), (0, 2), (0, 16), (1, 155), (1, 26), (1, 211), (0, 5), (0, 227), (1, 96), (0, 141), (0, 2), (0, 211), (0, 18), (0, 19), (0, 17), (2, 105), (3, 1), (0, 28), (0, 9), (0, 242), (0, 27), (0, 17), (0, 68), (6, 41), (8, 57), (0, 125), (20, 219), (0, 54), (3, 93), (0, 29), (7, 60), (3, 192), (2, 96), (0, 262), (5, 50), (0, 3), (0, 111), (0, 39), (0, 17), (0, 1), (0, 217), (0, 0), (3, 159), (0, 20), (3, 57), (0, 52), (14, 224), (0, 0), (9, 236), (20, 213), (4, 265), ], entries: &[ ("order", AttributeId::Order), ("lengthAdjust", AttributeId::LengthAdjust), ("offset", AttributeId::Offset), ("cy", AttributeId::Cy), ("azimuth", AttributeId::Azimuth), ("viewTarget", AttributeId::ViewTarget), ("systemLanguage", AttributeId::SystemLanguage), ("exponent", AttributeId::Exponent), ("targetX", AttributeId::TargetX), ("specularConstant", AttributeId::SpecularConstant), ("gradientTransform", AttributeId::GradientTransform), ("onclick", AttributeId::Onclick), ("opacity", AttributeId::Opacity), ("string", AttributeId::String), ("onbegin", AttributeId::Onbegin), ("k4", AttributeId::K4), ("clip-path", AttributeId::ClipPath), ("bias", AttributeId::Bias), ("font", AttributeId::Font), ("dx", AttributeId::Dx), ("y1", AttributeId::Y1), ("keyTimes", AttributeId::KeyTimes), ("markerUnits", AttributeId::MarkerUnits), ("stdDeviation", AttributeId::StdDeviation), ("baseProfile", AttributeId::BaseProfile), ("class", AttributeId::Class), ("vert-adv-y", AttributeId::VertAdvY), ("widths", AttributeId::Widths), ("vert-origin-x", AttributeId::VertOriginX), ("color-profile", AttributeId::ColorProfile), ("k1", AttributeId::K1), ("path", AttributeId::Path), ("markerWidth", AttributeId::MarkerWidth), ("surfaceScale", AttributeId::SurfaceScale), ("space", AttributeId::Space), ("onmouseup", AttributeId::Onmouseup), ("y", AttributeId::Y), ("text-decoration", AttributeId::TextDecoration), ("patternUnits", AttributeId::PatternUnits), ("lighting-color", AttributeId::LightingColor), ("color-interpolation-filters", AttributeId::ColorInterpolationFilters), ("bbox", AttributeId::Bbox), ("startOffset", AttributeId::StartOffset), ("to", AttributeId::To), ("version", AttributeId::Version), ("targetY", AttributeId::TargetY), ("style", AttributeId::Style), ("method", AttributeId::Method), ("stemh", AttributeId::Stemh), ("x", AttributeId::X), ("externalResourcesRequired", AttributeId::ExternalResourcesRequired), ("k", AttributeId::K), ("onmousemove", AttributeId::Onmousemove), ("format", AttributeId::Format), ("role", AttributeId::Role), ("specularExponent", AttributeId::SpecularExponent), ("patternTransform", AttributeId::PatternTransform), ("points", AttributeId::Points), ("markerHeight", AttributeId::MarkerHeight), ("stitchTiles", AttributeId::StitchTiles), ("fill-rule", AttributeId::FillRule), ("g1", AttributeId::G1), ("unicode", AttributeId::Unicode), ("horiz-origin-y", AttributeId::HorizOriginY), ("amplitude", AttributeId::Amplitude), ("baseFrequency", AttributeId::BaseFrequency), ("from", AttributeId::From), ("text-anchor", AttributeId::TextAnchor), ("kernelMatrix", AttributeId::KernelMatrix), ("clipPathUnits", AttributeId::ClipPathUnits), ("panose-1", AttributeId::Panose1), ("pointer-events", AttributeId::PointerEvents), ("radius", AttributeId::Radius), ("local", AttributeId::Local), ("type", AttributeId::Type), ("min", AttributeId::Min), ("id", AttributeId::Id), ("kernelUnitLength", AttributeId::KernelUnitLength), ("descent", AttributeId::Descent), ("direction", AttributeId::Direction), ("encoding", AttributeId::Encoding), ("calcMode", AttributeId::CalcMode), ("attributeType", AttributeId::AttributeType), ("onfocusin", AttributeId::Onfocusin), ("values", AttributeId::Values), ("baseline-shift", AttributeId::BaselineShift), ("u2", AttributeId::U2), ("letter-spacing", AttributeId::LetterSpacing), ("fill-opacity", AttributeId::FillOpacity), ("image-rendering", AttributeId::ImageRendering), ("flood-opacity", AttributeId::FloodOpacity), ("xmlns", AttributeId::Xmlns), ("spacing", AttributeId::Spacing), ("rx", AttributeId::Rx), ("repeatCount", AttributeId::RepeatCount), ("horiz-origin-x", AttributeId::HorizOriginX), ("stroke", AttributeId::Stroke), ("ry", AttributeId::Ry), ("href", AttributeId::Href), ("maskUnits", AttributeId::MaskUnits), ("font-family", AttributeId::FontFamily), ("onmousedown", AttributeId::Onmousedown), ("xChannelSelector", AttributeId::XChannelSelector), ("height", AttributeId::Height), ("v-hanging", AttributeId::VHanging), ("arabic-form", AttributeId::ArabicForm), ("max", AttributeId::Max), ("target", AttributeId::Target), ("filterUnits", AttributeId::FilterUnits), ("scale", AttributeId::Scale), ("clip", AttributeId::Clip), ("onmouseover", AttributeId::Onmouseover), ("show", AttributeId::Show), ("refX", AttributeId::RefX), ("overline-position", AttributeId::OverlinePosition), ("origin", AttributeId::Origin), ("arcrole", AttributeId::Arcrole), ("stroke-opacity", AttributeId::StrokeOpacity), ("filterRes", AttributeId::FilterRes), ("contentScriptType", AttributeId::ContentScriptType), ("seed", AttributeId::Seed), ("onactivate", AttributeId::Onactivate), ("shape-rendering", AttributeId::ShapeRendering), ("v-mathematical", AttributeId::VMathematical), ("vert-origin-y", AttributeId::VertOriginY), ("pointsAtX", AttributeId::PointsAtX), ("stroke-width", AttributeId::StrokeWidth), ("dominant-baseline", AttributeId::DominantBaseline), ("divisor", AttributeId::Divisor), ("in2", AttributeId::In2), ("onresize", AttributeId::Onresize), ("width", AttributeId::Width), ("y2", AttributeId::Y2), ("strikethrough-position", AttributeId::StrikethroughPosition), ("font-size-adjust", AttributeId::FontSizeAdjust), ("glyph-orientation-vertical", AttributeId::GlyphOrientationVertical), ("onfocusout", AttributeId::Onfocusout), ("pathLength", AttributeId::PathLength), ("fill", AttributeId::Fill), ("color-interpolation", AttributeId::ColorInterpolation), ("yChannelSelector", AttributeId::YChannelSelector), ("rendering-intent", AttributeId::RenderingIntent), ("contentStyleType", AttributeId::ContentStyleType), ("font-style", AttributeId::FontStyle), ("base", AttributeId::Base), ("writing-mode", AttributeId::WritingMode), ("unicode-bidi", AttributeId::UnicodeBidi), ("refY", AttributeId::RefY), ("title", AttributeId::Title), ("result", AttributeId::Result), ("cx", AttributeId::Cx), ("flood-color", AttributeId::FloodColor), ("stroke-dashoffset", AttributeId::StrokeDashoffset), ("edgeMode", AttributeId::EdgeMode), ("font-weight", AttributeId::FontWeight), ("lang", AttributeId::Lang), ("requiredExtensions", AttributeId::RequiredExtensions), ("orient", AttributeId::Orient), ("marker", AttributeId::Marker), ("font-size", AttributeId::FontSize), ("onmouseout", AttributeId::Onmouseout), ("font-variant", AttributeId::FontVariant), ("onzoom", AttributeId::Onzoom), ("limitingConeAngle", AttributeId::LimitingConeAngle), ("onrepeat", AttributeId::Onrepeat), ("in", AttributeId::In), ("pointsAtZ", AttributeId::PointsAtZ), ("zoomAndPan", AttributeId::ZoomAndPan), ("mask", AttributeId::Mask), ("begin", AttributeId::Begin), ("v-alphabetic", AttributeId::VAlphabetic), ("keyPoints", AttributeId::KeyPoints), ("g2", AttributeId::G2), ("glyphRef", AttributeId::GlyphRef), ("name", AttributeId::Name), ("visibility", AttributeId::Visibility), ("ideographic", AttributeId::Ideographic), ("font-stretch", AttributeId::FontStretch), ("display", AttributeId::Display), ("overflow", AttributeId::Overflow), ("stroke-linecap", AttributeId::StrokeLinecap), ("color-rendering", AttributeId::ColorRendering), ("stop-opacity", AttributeId::StopOpacity), ("accumulate", AttributeId::Accumulate), ("word-spacing", AttributeId::WordSpacing), ("glyph-orientation-horizontal", AttributeId::GlyphOrientationHorizontal), ("tableValues", AttributeId::TableValues), ("line-height", AttributeId::LineHeight), ("kerning", AttributeId::Kerning), ("enable-background", AttributeId::EnableBackground), ("x-height", AttributeId::XHeight), ("gradientUnits", AttributeId::GradientUnits), ("stemv", AttributeId::Stemv), ("viewBox", AttributeId::ViewBox), ("glyph-name", AttributeId::GlyphName), ("k3", AttributeId::K3), ("textLength", AttributeId::TextLength), ("preserveAlpha", AttributeId::PreserveAlpha), ("onload", AttributeId::Onload), ("operator", AttributeId::Operator), ("horiz-adv-x", AttributeId::HorizAdvX), ("v-ideographic", AttributeId::VIdeographic), ("cap-height", AttributeId::CapHeight), ("restart", AttributeId::Restart), ("fy", AttributeId::Fy), ("intercept", AttributeId::Intercept), ("fx", AttributeId::Fx), ("alphabetic", AttributeId::Alphabetic), ("onscroll", AttributeId::Onscroll), ("end", AttributeId::End), ("color", AttributeId::Color), ("accent-height", AttributeId::AccentHeight), ("patternContentUnits", AttributeId::PatternContentUnits), ("numOctaves", AttributeId::NumOctaves), ("x2", AttributeId::X2), ("units-per-em", AttributeId::UnitsPerEm), ("stroke-linejoin", AttributeId::StrokeLinejoin), ("attributeName", AttributeId::AttributeName), ("mathematical", AttributeId::Mathematical), ("underline-thickness", AttributeId::UnderlineThickness), ("ascent", AttributeId::Ascent), ("orientation", AttributeId::Orientation), ("keySplines", AttributeId::KeySplines), ("hanging", AttributeId::Hanging), ("z", AttributeId::Z), ("primitiveUnits", AttributeId::PrimitiveUnits), ("diffuseConstant", AttributeId::DiffuseConstant), ("strikethrough-thickness", AttributeId::StrikethroughThickness), ("cursor", AttributeId::Cursor), ("marker-end", AttributeId::MarkerEnd), ("clip-rule", AttributeId::ClipRule), ("marker-mid", AttributeId::MarkerMid), ("x1", AttributeId::X1), ("requiredFeatures", AttributeId::RequiredFeatures), ("alignment-baseline", AttributeId::AlignmentBaseline), ("unicode-range", AttributeId::UnicodeRange), ("stop-color", AttributeId::StopColor), ("elevation", AttributeId::Elevation), ("k2", AttributeId::K2), ("dy", AttributeId::Dy), ("mode", AttributeId::Mode), ("repeatDur", AttributeId::RepeatDur), ("actuate", AttributeId::Actuate), ("by", AttributeId::By), ("pointsAtY", AttributeId::PointsAtY), ("r", AttributeId::R), ("additive", AttributeId::Additive), ("rotate", AttributeId::Rotate), ("slope", AttributeId::Slope), ("media", AttributeId::Media), ("maskContentUnits", AttributeId::MaskContentUnits), ("spreadMethod", AttributeId::SpreadMethod), ("filter", AttributeId::Filter), ("text-rendering", AttributeId::TextRendering), ("underline-position", AttributeId::UnderlinePosition), ("onabort", AttributeId::Onabort), ("dur", AttributeId::Dur), ("xlink", AttributeId::Xlink), ("stroke-miterlimit", AttributeId::StrokeMiterlimit), ("onerror", AttributeId::Onerror), ("onend", AttributeId::Onend), ("stroke-dasharray", AttributeId::StrokeDasharray), ("d", AttributeId::D), ("preserveAspectRatio", AttributeId::PreserveAspectRatio), ("standalone", AttributeId::Standalone), ("transform", AttributeId::Transform), ("marker-start", AttributeId::MarkerStart), ("u1", AttributeId::U1), ("overline-thickness", AttributeId::OverlineThickness), ("onunload", AttributeId::Onunload), ], }; impl AttributeId { /// Parses `AttributeId` from a string. pub fn from_str(text: &str) -> Option { ATTRIBUTES.get(text).cloned() } /// Returns an original string. pub fn as_str(&self) -> &'static str { ATTRIBUTES.key(self) } } impl fmt::Debug for AttributeId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl fmt::Display for AttributeId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.as_str()) } } // A stripped down `phf` crate fork. // // https://github.com/sfackler/rust-phf struct Map { pub key: u64, pub disps: &'static [(u32, u32)], pub entries: &'static[(&'static str, V)], } impl Map { fn get(&self, key: &str) -> Option<&V> { use std::borrow::Borrow; let hash = hash(key, self.key); let index = get_index(hash, &*self.disps, self.entries.len()); let entry = &self.entries[index as usize]; let b = entry.0.borrow(); if b == key { Some(&entry.1) } else { None } } fn key(&self, value: &V) -> &'static str { self.entries.iter().find(|kv| kv.1 == *value).unwrap().0 } } #[inline] fn hash(x: &str, key: u64) -> u64 { use std::hash::Hasher; let mut hasher = siphasher::sip::SipHasher13::new_with_keys(0, key); hasher.write(x.as_bytes()); hasher.finish() } #[inline] fn get_index(hash: u64, disps: &[(u32, u32)], len: usize) -> u32 { let (g, f1, f2) = split(hash); let (d1, d2) = disps[(g % (disps.len() as u32)) as usize]; displace(f1, f2, d1, d2) % (len as u32) } #[inline] fn split(hash: u64) -> (u32, u32, u32) { const BITS: u32 = 21; const MASK: u64 = (1 << BITS) - 1; ((hash & MASK) as u32, ((hash >> BITS) & MASK) as u32, ((hash >> (2 * BITS)) & MASK) as u32) } #[inline] fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 { d2 + f1 * d1 + f2 } svgdom-0.18.0/src/node.rs010064400017500001750000000455251352165555400134300ustar0000000000000000use std::iter::FilterMap; use std::cell::{Ref, RefMut}; use crate::{ tree, Attribute, AttributeId, AttributeQName, AttributeQNameRef, Attributes, AttributeValue, ElementId, Error, NodeData, NodeType, PaintFallback, QName, QNameRef, TagName, TagNameRef, }; impl<'a, N, V> From<(N, V)> for Attribute where AttributeQNameRef<'a>: From, AttributeValue: From { fn from(v: (N, V)) -> Self { Attribute::new(v.0, v.1) } } impl<'a, N> From<(N, Node)> for Attribute where AttributeQNameRef<'a>: From, N: Clone { fn from(v: (N, Node)) -> Self { let n = AttributeQNameRef::from(v.0.clone()); if n.has_id(AttributeId::Href) { Attribute::new(v.0, AttributeValue::Link(v.1)) } else if n.has_id(AttributeId::Fill) || n.has_id(AttributeId::Stroke) { Attribute::new(v.0, AttributeValue::Paint(v.1, None)) } else { Attribute::new(v.0, AttributeValue::FuncLink(v.1)) } } } impl<'a, N> From<(N, (Node, Option))> for Attribute where AttributeQNameRef<'a>: From, N: Clone { fn from(v: (N, (Node, Option))) -> Self { Attribute::new(v.0, AttributeValue::Paint((v.1).0, (v.1).1)) } } /// Representation of the SVG node. /// /// This is the main block of the library. /// /// It's designed as classical DOM node. It has links to a parent node, first child, last child, /// previous sibling and next sibling. So DOM nodes manipulations are very fast. /// /// Node consists of: /// /// - The [`NodeType`], which indicates it's type. It can't be changed. /// - Optional [`TagName`], used only by element nodes. /// - Unique ID of the `Element` node. Can be set to nodes with other types, /// but without any affect. /// - [`Attributes`] - list of [`Attribute`]s. /// - List of linked nodes. [Details.](#method.set_attribute_checked) /// - Text data, which is used by non-element nodes. Empty by default. /// /// [`Attribute`]: struct.Attribute.html /// [`Attributes`]: struct.Attributes.html /// [`NodeType`]: enum.NodeType.html /// [`TagName`]: type.TagName.html pub type Node = tree::Node; impl Node { /// Returns `true` if the node has a parent node. /// /// This method ignores root node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. /// /// # Examples /// ``` /// use svgdom::Document; /// /// let doc = Document::from_str( /// " /// /// ").unwrap(); /// /// let svg = doc.root().first_child().unwrap(); /// let rect = svg.first_child().unwrap(); /// assert_eq!(svg.has_parent(), false); /// assert_eq!(rect.has_parent(), true); /// ``` pub fn has_parent(&self) -> bool { match self.parent() { Some(node) => !node.is_root(), None => false, } } /// Returns node's type. /// /// You can't change the type of the node. Only create a new one. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn node_type(&self) -> NodeType { self.borrow().node_type } /// Returns `true` if current node is a Root node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn is_root(&self) -> bool { self.node_type() == NodeType::Root } /// Returns `true` if current node is an Element node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn is_element(&self) -> bool { self.node_type() == NodeType::Element } /// Returns `true` if current node is a Comment node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn is_comment(&self) -> bool { self.node_type() == NodeType::Comment } /// Returns `true` if current node is a Text node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn is_text(&self) -> bool { self.node_type() == NodeType::Text } /// Checks that node belongs to any `Document`. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn is_detached(&self) -> bool { self.borrow().storage_key.is_none() } /// Returns a text data of the node. /// /// Nodes with `Element` type can't contain text data. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn text(&self) -> Ref { Ref::map(self.borrow(), |d| &d.text) } /// Returns a mutable text data of the node. /// /// Nodes with `Element` type can't contain text data. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn text_mut(&mut self) -> RefMut { RefMut::map(self.borrow_mut(), |d| &mut d.text) } /// Sets a text data to the node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn set_text(&mut self, text: &str) { debug_assert_ne!(self.node_type(), NodeType::Element); self.borrow_mut().text = text.to_owned(); } /// Returns an ID of the element node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn id(&self) -> Ref { Ref::map(self.borrow(), |d| &d.id) } /// Returns `true` if node has a not empty ID. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn has_id(&self) -> bool { !self.id().is_empty() } /// Sets an ID of the element. /// /// Only element nodes can contain an ID. /// /// # Panics /// /// Panics if the node is currently borrowed. pub fn set_id>(&mut self, id: S) { // TODO: check that it's unique. debug_assert_eq!(self.node_type(), NodeType::Element); self.borrow_mut().id = id.into().to_owned(); } /// Returns `true` if node has an `Element` type and an SVG tag name. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn is_svg_element(&self) -> bool { if !self.is_element() { return false; } match self.borrow().tag_name { QName::Id(_) => true, QName::Name(_) => false, } } /// Returns a tag name of the element node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn tag_name(&self) -> Ref { Ref::map(self.borrow(), |d| &d.tag_name) } /// Returns a tag name id of the SVG element node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn tag_id(&self) -> Option { match self.borrow().tag_name { QName::Id(id) => Some(id), QName::Name(_) => None, } } /// Returns `true` if node has the same tag name as supplied. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn has_tag_name<'a, T>(&self, tag_name: T) -> bool where TagNameRef<'a>: From { self.borrow().tag_name.as_ref() == TagNameRef::from(tag_name) } /// Sets a tag name of the element node. /// /// Only element nodes can contain tag name. /// /// # Panics /// /// - Panics if the node is currently borrowed. /// - Panics if a string tag name is empty. pub fn set_tag_name<'a, T>(&mut self, tag_name: T) where TagNameRef<'a>: From { debug_assert_eq!(self.node_type(), NodeType::Element); let tn = TagNameRef::from(tag_name); if let QNameRef::Name(name) = tn { if name.is_empty() { panic!("supplied tag name is empty"); } } self.borrow_mut().tag_name = TagName::from(tn); } /// Returns a reference to the `Attributes` of the current node. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn attributes(&self) -> Ref { Ref::map(self.borrow(), |d| &d.attributes) } /// Returns a mutable reference to the `Attributes` of the current node. /// /// # Panics /// /// Panics if the node is currently borrowed. pub fn attributes_mut(&mut self) -> RefMut { RefMut::map(self.borrow_mut(), |d| &mut d.attributes) } /// Returns `true` if the node has an attribute with such `id`. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. #[inline] pub fn has_attribute<'a, N>(&self, name: N) -> bool where AttributeQNameRef<'a>: From { self.borrow().attributes.contains(name) } /// Inserts a new attribute into attributes list. /// /// Unwrapped version of the [`set_attribute_checked`] method. /// /// # Panics /// /// Will panic on any error produced by the [`set_attribute_checked`] method. /// /// [`set_attribute_checked`]: #method.set_attribute_checked pub fn set_attribute(&mut self, v: T) where T: Into { self.set_attribute_checked(v).unwrap(); } /// Inserts a new attribute into attributes list. /// /// You can set attribute using one of the possible combinations: /// /// - ([`AttributeId`]/`&str`, [`AttributeValue`]) /// - ([`AttributeId`], [`Node`]) /// - [`Attribute`] /// /// [`AttributeId`]: enum.AttributeId.html /// [`Attribute`]: struct.Attribute.html /// [`Node`]: type.Node.html /// [`AttributeValue`]: enum.AttributeValue.html /// /// This method will overwrite an existing attribute with the same name. /// /// # Errors /// /// - [`ElementMustHaveAnId`] /// - [`ElementCrosslink`] /// /// # Panics /// /// Panics if the node is currently borrowed. /// /// # Examples /// /// Ways to specify attributes: /// /// ``` /// use svgdom::{ /// Document, /// Attribute, /// AttributeId as AId, /// ElementId as EId, /// }; /// /// // Create a simple document. /// let mut doc = Document::new(); /// let mut svg = doc.create_element(EId::Svg); /// let mut rect = doc.create_element(EId::Rect); /// /// doc.root().append(svg.clone()); /// svg.append(rect.clone()); /// /// // In order to set element as an attribute value, we must set id first. /// rect.set_id("rect1"); /// /// // Using predefined attribute name. /// svg.set_attribute((AId::X, 1.0)); /// svg.set_attribute((AId::X, "random text")); /// // Using custom attribute name. /// svg.set_attribute(("non-svg-attr", 1.0)); /// // Using existing attribute object. /// svg.set_attribute(Attribute::new(AId::X, 1.0)); /// svg.set_attribute(Attribute::new("non-svg-attr", 1.0)); /// // Using an existing node as an attribute value. /// svg.set_attribute((AId::Href, rect)); /// ``` /// /// Linked attributes: /// /// ``` /// use svgdom::{ /// Document, /// AttributeId as AId, /// ElementId as EId, /// AttributeValue, /// }; /// /// // Create a simple document. /// let mut doc = Document::new(); /// let mut gradient = doc.create_element(EId::LinearGradient); /// let mut rect = doc.create_element(EId::Rect); /// /// doc.root().append(gradient.clone()); /// doc.root().append(rect.clone()); /// /// gradient.set_id("lg1"); /// rect.set_id("rect1"); /// /// // Set a `fill` attribute value to the `none`. /// // For now everything like in any other XML DOM library. /// rect.set_attribute((AId::Fill, AttributeValue::None)); /// /// // Now we want to fill our rect with a gradient. /// // To do this we need to set a link attribute: /// rect.set_attribute((AId::Fill, gradient.clone())); /// /// // Now our fill attribute has a link to the `gradient` node. /// // Not as text, aka `url(#lg1)`, but as actual reference. /// /// // This adds support for fast checking that the element is used. Which is very useful. /// /// // `gradient` is now used, since we link it. /// assert_eq!(gradient.is_used(), true); /// // Also, we can check how many elements are uses this `gradient`. /// assert_eq!(gradient.uses_count(), 1); /// // And even get this elements. /// assert_eq!(gradient.linked_nodes()[0], rect); /// /// // And now, if we remove our `rect` - `gradient` will became unused again. /// doc.remove_node(rect); /// assert_eq!(gradient.is_used(), false); /// ``` /// /// [`ElementMustHaveAnId`]: enum.Error.html /// [`ElementCrosslink`]: enum.Error.html pub fn set_attribute_checked(&mut self, v: T) -> Result<(), Error> where T: Into { self.set_attribute_checked_impl(v.into()) } fn set_attribute_checked_impl(&mut self, attr: Attribute) -> Result<(), Error> { debug_assert!(self.node_type() == NodeType::Element); match attr.value { AttributeValue::Link(ref iri) | AttributeValue::FuncLink(ref iri) => { self.set_link_attribute(attr.name, iri.clone(), None)?; return Ok(()); } AttributeValue::Paint(ref iri, fallback) => { self.set_link_attribute(attr.name, iri.clone(), fallback)?; return Ok(()); } _ => {} } self.set_simple_attribute(attr); Ok(()) } fn set_simple_attribute(&mut self, attr: Attribute) { debug_assert!(!attr.is_link_container()); // we must remove existing attribute to prevent dangling links self.remove_attribute(attr.name.as_ref()); let mut attrs = self.attributes_mut(); attrs.insert(attr); } fn set_link_attribute( &mut self, name: AttributeQName, mut node: Node, fallback: Option, ) -> Result<(), Error> { if node.id().is_empty() { return Err(Error::ElementMustHaveAnId); } // check for recursion if *self.id() == *node.id() { return Err(Error::ElementCrosslink); } // check for recursion 2 if self.linked_nodes().iter().any(|n| *n == node) { return Err(Error::ElementCrosslink); } // we must remove existing attribute to prevent dangling links self.remove_attribute(name.as_ref()); { let a = if name.has_id(AttributeId::Href) { Attribute::new(name.as_ref(), AttributeValue::Link(node.clone())) } else if name.has_id(AttributeId::Fill) || name.has_id(AttributeId::Stroke) { Attribute::new(name.as_ref(), AttributeValue::Paint(node.clone(), fallback)) } else { Attribute::new(name.as_ref(), AttributeValue::FuncLink(node.clone())) }; let mut attributes = self.attributes_mut(); attributes.insert(a); } node.borrow_mut().linked_nodes.push(self.clone()); Ok(()) } /// Inserts a new attribute into attributes list if it doesn't contain one. /// /// `value` will be cloned if needed. /// /// Shorthand for: /// /// ```ignore /// if !node.has_attribute(...) { /// node.set_attribute(...); /// } /// ``` /// /// # Panics /// /// Will panic on any error produced by the [`set_attribute_checked`] method. /// /// [`set_attribute_checked`]: #method.set_attribute_checked pub fn set_attribute_if_none<'a, T>(&mut self, v: T) where T: Into { let attr: Attribute = v.into(); if !self.has_attribute(attr.name.as_ref()) { self.set_attribute(attr); } } /// Removes an attribute from the node. /// /// It will also unlink it, if it was an referenced attribute. /// /// # Panics /// /// Panics if the node is currently borrowed. pub fn remove_attribute<'a, N>(&mut self, name: N) where AttributeQNameRef<'a>: From, N: Copy { if !self.has_attribute(name) { return; } // we must unlink referenced attributes if let Some(value) = self.attributes().get_value(name) { match *value { AttributeValue::Link(ref node) | AttributeValue::FuncLink(ref node) | AttributeValue::Paint(ref node, _) => { let mut node = node.clone(); // this code can't panic, because we know that such node exist let index = node.borrow().linked_nodes.iter().position(|n| n == self).unwrap(); node.borrow_mut().linked_nodes.remove(index); } _ => {} } } self.attributes_mut().remove(name); } /// Returns an iterator over linked nodes. /// /// See [Node::set_attribute()](#method.set_attribute) for details. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn linked_nodes(&self) -> Ref> { Ref::map(self.borrow(), |d| &d.linked_nodes) } /// Returns an iterator over mutable linked nodes. /// /// See [Node::set_attribute()](#method.set_attribute) for details. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn linked_nodes_mut(&mut self) -> RefMut> { RefMut::map(self.borrow_mut(), |d| &mut d.linked_nodes) } /// Returns `true` if the current node is linked to any of the DOM nodes. /// /// See [Node::set_attribute()](#method.set_attribute) for details. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn is_used(&self) -> bool { !self.linked_nodes().is_empty() } /// Returns a number of nodes, which is linked to this node. /// /// See [Node::set_attribute()](#method.set_attribute) for details. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn uses_count(&self) -> usize { self.linked_nodes().len() } } /// An iterator over SVG elements. pub trait FilterSvg: Iterator { /// Filters SVG elements. fn svg(self) -> FilterMap Option<(ElementId, Node)>> where Self: Iterator + Sized, { fn is_svg(node: Node) -> Option<(ElementId, Node)> { if let QName::Id(id) = *node.tag_name() { return Some((id, node.clone())); } None } self.filter_map(is_svg) } } impl<'a, I: Iterator> FilterSvg for I {} svgdom-0.18.0/src/parser/mod.rs010064400017500001750000000566421352304206200145420ustar0000000000000000use std::str::{self, FromStr}; use log::warn; use svgtypes::{ Paint, PaintFallback, PathParser, Stream, }; use super::*; mod text; struct Link { attr_id: AttributeId, iri: String, fallback: Option, node: Node, } /// List of all parsed IRI and FuncIRI. struct Links { list: Vec, } impl Links { fn append( &mut self, id: AttributeId, iri: &str, fallback: Option, node: &Node, ) { self.list.push(Link { attr_id: id, iri: iri.to_string(), fallback, node: node.clone(), }); } } pub fn parse_svg(text: &str) -> Result { let ro_doc = roxmltree::Document::parse(text)?; let mut links = Links { list: Vec::new() }; let mut doc = Document::new(); let root = doc.root(); let mut parent = root.clone(); let style_sheet = resolve_css(&ro_doc); for child in ro_doc.root().children() { process_node(&ro_doc, child, &style_sheet, &mut links, &mut doc, &mut parent)?; } // First element must be an 'svg' element. if doc.svg_element().is_none() { return Err(ParserError::NoSvgElement); } // Remove 'style' elements, because their content (CSS) // is stored separately and will be processed later. doc.drain(root.clone(), |n| n.has_tag_name(ElementId::Style)); resolve_links(&doc, &mut links); text::prepare_text(&mut doc); Ok(doc) } fn process_node( ro_doc: &roxmltree::Document, xml_node: roxmltree::Node, style_sheet: &simplecss::StyleSheet, links: &mut Links, doc: &mut Document, parent: &mut Node, ) -> Result<(), ParserError> { match xml_node.node_type() { roxmltree::NodeType::Element => { if xml_node.tag_name().namespace() != Some("http://www.w3.org/2000/svg") { return Ok(()); } let tag_name = xml_node.tag_name(); let local = tag_name.name(); let mut e = match ElementId::from_str(local) { Some(eid) => { doc.create_element(eid) } None => { return Ok(()); } }; for attr in xml_node.attributes() { match attr.namespace() { None | Some("http://www.w3.org/2000/svg") | Some("http://www.w3.org/1999/xlink") | Some("http://www.w3.org/XML/1998/namespace") => {} _ => continue, } if let Some(aid) = AttributeId::from_str(attr.name()) { parse_svg_attribute(ro_doc, aid, attr.value(), attr.value_range().start, &mut e, links)?; } } for rule in &style_sheet.rules { if rule.selector.matches(&XmlNode(xml_node)) { for declaration in &rule.declarations { parse_css_attribute_value( ro_doc, declaration.name, declaration.value, &mut e, links, )?; } } } if let Some(attr) = xml_node.attribute_node("style") { parse_style_attribute(&ro_doc, attr.value(), attr.value_range().start, &mut e, links)?; } parent.append(e.clone()); if xml_node.is_element() && xml_node.has_children() { for child in xml_node.children() { process_node(ro_doc, child, style_sheet, links, doc, &mut e)?; } } } roxmltree::NodeType::Text => { let text = xml_node.text().unwrap(); if text.trim().is_empty() { // Whitespaces inside text elements are important. if let Some(id) = parent.tag_id() { match id { ElementId::Text | ElementId::Tspan | ElementId::Tref => { let n = doc.create_node(NodeType::Text, text); parent.append(n); } _ => {} } } } else { let n = doc.create_node(NodeType::Text, xml_node.text().unwrap()); parent.append(n); } } roxmltree::NodeType::Comment => { let n = doc.create_node(NodeType::Comment, xml_node.text().unwrap()); parent.append(n); } _ => {} } // Check that the first element of the doc is 'svg'. // // Check only when we parsing the root nodes, which is faster. if parent.is_root() { if let Some((id, _)) = doc.root().children().svg().nth(0) { if id != ElementId::Svg { return Err(ParserError::NoSvgElement); } } } Ok(()) } fn parse_svg_attribute<'a>( ro_doc: &roxmltree::Document, id: AttributeId, value: &'a str, value_pos: usize, node: &mut Node, links: &mut Links, ) -> Result<(), ParserError> { match id { AttributeId::Id => { node.set_id(value); } AttributeId::Style | AttributeId::Class => { // Ignore these attributes. } _ => { parse_svg_attribute_value(ro_doc, id, value, value_pos, node, links)?; } } Ok(()) } fn parse_svg_attribute_value<'a>( ro_doc: &roxmltree::Document, id: AttributeId, value: &'a str, value_pos: usize, node: &mut Node, links: &mut Links, ) -> Result<(), ParserError> { let av = _parse_svg_attribute_value(ro_doc, id, value, value_pos, node, links); match av { Ok(av) => { if let Some(av) = av { match av { AttributeValue::NumberList(ref list) if list.is_empty() => {} AttributeValue::LengthList(ref list) if list.is_empty() => {} AttributeValue::Path(ref path) if path.is_empty() => {} _ => node.set_attribute((id, av)), } } } Err(_) => { warn!("Attribute '{}' has an invalid value: '{}'.", id, value); } } Ok(()) } fn parse_css_attribute_value<'a>( ro_doc: &roxmltree::Document, name: &str, value: &str, node: &mut Node, links: &mut Links, ) -> Result<(), ParserError> { if let Some(id) = AttributeId::from_str(name) { // Parse only the presentation attributes. // `transform` isn't a presentation attribute, but should be parsed anyway. if !id.is_presentation() && id != AttributeId::Transform { return Ok(()); } let mut parse_attr = |aid| { parse_svg_attribute_value(ro_doc, aid, value, 0, node, links) }; if id == AttributeId::Marker { // The SVG specification defines three properties to reference markers: // `marker-start`, `marker-mid`, `marker-end`. // It also provides a shorthand property, marker. // Using the marker property from a style sheet // is equivalent to using all three (start, mid, end). // However, a shorthand property cannot be used as a presentation attribute. // So we have to convert it into presentation attributes. parse_attr(AttributeId::MarkerStart)?; parse_attr(AttributeId::MarkerMid)?; parse_attr(AttributeId::MarkerEnd)?; } else { parse_attr(id)?; } } Ok(()) } #[inline] fn f64_bound(min: f64, val: f64, max: f64) -> f64 { if val > max { return max; } else if val < min { return min; } val } fn _parse_svg_attribute_value<'a>( ro_doc: &roxmltree::Document, aid: AttributeId, value: &'a str, value_pos: usize, node: &mut Node, links: &mut Links, ) -> Result, svgtypes::Error> { use crate::AttributeId as AId; let eid = node.tag_id().unwrap(); // 'unicode' attribute can contain spaces. let value = if aid != AId::Unicode { value.trim() } else { value }; let av = match aid { AId::Href => { match Stream::from(value).parse_iri() { Ok(link) => { // Collect links for later processing. links.append(aid, link, None, node); return Ok(None); } Err(_) => { return Ok(Some(AttributeValue::String(value.to_string()))); } } } AId::X | AId::Y | AId::Dx | AId::Dy => { // Some attributes can contain different data based on the element type. match eid { ElementId::AltGlyph | ElementId::Text | ElementId::Tref | ElementId::Tspan => { AttributeValue::LengthList(LengthList::from_str(value)?) } _ => { AttributeValue::Length(Length::from_str(value)?) } } } AId::X1 | AId::Y1 | AId::X2 | AId::Y2 | AId::R | AId::Rx | AId::Ry | AId::Cx | AId::Cy | AId::Fx | AId::Fy | AId::RefX | AId::RefY | AId::Width | AId::Height | AId::MarkerWidth | AId::MarkerHeight | AId::StartOffset => { AttributeValue::Length(Length::from_str(value)?) } AId::Offset => { // offset = | let l = Length::from_str(value)?; if l.unit == LengthUnit::None || l.unit == LengthUnit::Percent { AttributeValue::Length(l) } else { return Err(svgtypes::Error::InvalidValue); } } AId::StrokeDashoffset | AId::StrokeWidth => { match value { "inherit" => AttributeValue::Inherit, _ => Length::from_str(value)?.into(), } } AId::StrokeMiterlimit => { match value { "inherit" => AttributeValue::Inherit, _ => parse_number(value)?.into(), } } AId::Opacity | AId::FillOpacity | AId::FloodOpacity | AId::StrokeOpacity | AId::StopOpacity => { match value { "inherit" => AttributeValue::Inherit, _ => { let n = parse_number(value)?; let n = f64_bound(0.0, n, 1.0); AttributeValue::Number(n) } } } AId::K1 | AId::K2 | AId::K3 | AId::K4 => { let n = parse_number(value)?; let n = f64_bound(0.0, n, 1.0); AttributeValue::Number(n) } AId::StrokeDasharray => { match value { "none" => AttributeValue::None, "inherit" => AttributeValue::Inherit, _ => AttributeValue::LengthList(LengthList::from_str(value)?), } } AId::Fill => { // 'fill' in animate-based elements it's another 'fill' // https://www.w3.org/TR/SVG/animate.html#FillAttribute match eid { ElementId::Set | ElementId::Animate | ElementId::AnimateColor | ElementId::AnimateMotion | ElementId::AnimateTransform => AttributeValue::String(value.to_string()), _ => { match Paint::from_str(value)? { Paint::None => AttributeValue::None, Paint::Inherit => AttributeValue::Inherit, Paint::CurrentColor => AttributeValue::CurrentColor, Paint::Color(color) => AttributeValue::Color(color), Paint::FuncIRI(link, fallback) => { // Collect links for later processing. links.append(aid, link, fallback, node); return Ok(None); } } } } } AId::Stroke => { match Paint::from_str(value)? { Paint::None => AttributeValue::None, Paint::Inherit => AttributeValue::Inherit, Paint::CurrentColor => AttributeValue::CurrentColor, Paint::Color(color) => AttributeValue::Color(color), Paint::FuncIRI(link, fallback) => { // Collect links for later processing. links.append(aid, link, fallback, node); return Ok(None); } } } AId::ClipPath | AId::Filter | AId::Marker | AId::MarkerEnd | AId::MarkerMid | AId::MarkerStart | AId::Mask => { match value { "none" => AttributeValue::None, "inherit" => AttributeValue::Inherit, _ => { let mut s = Stream::from(value); let link = s.parse_func_iri()?; // collect links for later processing links.append(aid, link, None, node); return Ok(None); } } } AId::Color => { match value { "inherit" => AttributeValue::Inherit, _ => AttributeValue::Color(Color::from_str(value)?), } } AId::LightingColor | AId::FloodColor | AId::StopColor => { match value { "inherit" => AttributeValue::Inherit, "currentColor" => AttributeValue::CurrentColor, _ => AttributeValue::Color(Color::from_str(value)?), } } AId::StdDeviation | AId::BaseFrequency | AId::Rotate => { // TODO: 'stdDeviation' can contain only one or two numbers AttributeValue::NumberList(NumberList::from_str(value)?) } AId::Points => { AttributeValue::Points(Points::from_str(value)?) } AId::D => { let mut data = Vec::new(); for token in PathParser::from(value) { match token { Ok(token) => data.push(token), Err(_) => { // By the SVG spec, any invalid data inside the path data // should stop parsing of this path, but not the whole document. let pos = ro_doc.text_pos_at(value_pos); warn!("A path attribute at {} was parsed partially \ due to an invalid data.", pos); break; } } } AttributeValue::Path(Path(data)) } AId::Transform | AId::GradientTransform | AId::PatternTransform => { let ts = Transform::from_str(value)?; if !ts.is_default() { AttributeValue::Transform(Transform::from_str(value)?) } else { return Ok(None); } } AId::FontSize => { match Length::from_str(value) { Ok(l) => AttributeValue::Length(l), Err(_) => { if value == "inherit" { AttributeValue::Inherit } else { AttributeValue::String(value.to_string()) } } } } AId::FontSizeAdjust => { match value { "none" => AttributeValue::None, "inherit" => AttributeValue::Inherit, _ => parse_number(value)?.into(), } } AId::Display | AId::PointerEvents | AId::TextDecoration => { match value { "none" => AttributeValue::None, "inherit" => AttributeValue::Inherit, _ => AttributeValue::String(value.to_string()), } } AId::ClipRule | AId::ColorInterpolation | AId::ColorInterpolationFilters | AId::ColorProfile | AId::ColorRendering | AId::Direction | AId::DominantBaseline | AId::EnableBackground | AId::FillRule | AId::FontFamily | AId::FontStretch | AId::FontStyle | AId::FontVariant | AId::FontWeight | AId::ImageRendering | AId::Kerning | AId::Overflow | AId::ShapeRendering | AId::StrokeLinecap | AId::StrokeLinejoin | AId::TextAnchor | AId::TextRendering | AId::UnicodeBidi | AId::Visibility | AId::WritingMode => { match value { "inherit" => AttributeValue::Inherit, _ => AttributeValue::String(value.to_string()), } } AId::LetterSpacing | AId::WordSpacing => { match value { "inherit" => AttributeValue::Inherit, "normal" => AttributeValue::String(value.to_string()), _ => AttributeValue::Length(Length::from_str(value)?), } } AId::BaselineShift => { match value { "inherit" => AttributeValue::Inherit, "baseline" | "sub" | "super" => AttributeValue::String(value.to_string()), _ => AttributeValue::Length(Length::from_str(value)?), } } AId::Orient => { match value { "auto" => AttributeValue::String(value.to_string()), _ => AttributeValue::Angle(Angle::from_str(value)?), } } AId::GlyphOrientationHorizontal => { match value { "inherit" => AttributeValue::Inherit, _ => AttributeValue::Angle(Angle::from_str(value)?), } } AId::GlyphOrientationVertical => { match value { "inherit" => AttributeValue::Inherit, "auto" => AttributeValue::String(value.to_string()), _ => AttributeValue::Angle(Angle::from_str(value)?), } } AId::ViewBox => { AttributeValue::ViewBox(ViewBox::from_str(value)?) } AId::PreserveAspectRatio => { AttributeValue::AspectRatio(AspectRatio::from_str(value)?) } _ => { AttributeValue::String(value.to_string()) } }; Ok(Some(av)) } fn parse_number(value: &str) -> Result { let mut s = Stream::from(value); let n = s.parse_number()?; if !s.at_end() { return Err(svgtypes::Error::InvalidValue); } Ok(n) } fn parse_style_attribute( ro_doc: &roxmltree::Document, value: &str, value_pos: usize, node: &mut Node, links: &mut Links, ) -> Result<(), ParserError> { for declaration in simplecss::DeclarationTokenizer::from(value) { if let Some(id) = AttributeId::from_str(declaration.name) { // Parse only the presentation attributes. // `transform` isn't a presentation attribute, but should be parsed anyway. if id.is_presentation() || id == AttributeId::Transform { parse_svg_attribute_value(ro_doc, id, declaration.value, value_pos, node, links)?; } } } Ok(()) } fn resolve_links(doc: &Document, links: &mut Links) { for d in &mut links.list { match doc.root().descendants().find(|n| *n.id() == d.iri) { Some(node) => { let res = if d.attr_id == AttributeId::Fill || d.attr_id == AttributeId::Stroke { d.node.set_attribute_checked((d.attr_id, (node.clone(), d.fallback))) } else { d.node.set_attribute_checked((d.attr_id, node.clone())) }; match res { Ok(_) => {} Err(Error::ElementMustHaveAnId) => { // TODO: unreachable? let attr = Attribute::from((d.attr_id, node.clone())); warn!("Element without an ID cannot be linked. \ Attribute {} ignored.", attr); } Err(Error::ElementCrosslink) => { let attr = Attribute::from((d.attr_id, node.clone())); warn!("Crosslink detected. Attribute {} ignored.", attr); } } } None => { let av = match d.fallback { Some(PaintFallback::None) => AttributeValue::None, Some(PaintFallback::CurrentColor) => AttributeValue::CurrentColor, Some(PaintFallback::Color(c)) => AttributeValue::Color(c), None => { if d.attr_id == AttributeId::Fill { warn!("Could not resolve a 'fill' IRI reference: {}. \ Fallback to 'none'.", d.iri); AttributeValue::None } else if d.attr_id == AttributeId::Href { warn!("Could not resolve an IRI reference: {}.", d.iri); AttributeValue::String(format!("#{}", d.iri)) } else { warn!("Could not resolve a FuncIRI reference: {}.", d.iri); AttributeValue::String(format!("url(#{})", d.iri)) } } }; d.node.set_attribute((d.attr_id, av)); } } } } struct XmlNode<'a, 'input: 'a>(pub roxmltree::Node<'a, 'input>); impl simplecss::Element for XmlNode<'_, '_> { fn parent_element(&self) -> Option { self.0.parent_element().map(XmlNode) } fn prev_sibling_element(&self) -> Option { self.0.prev_siblings().filter(|n| n.is_element()).nth(0).map(XmlNode) } fn has_local_name(&self, local_name: &str) -> bool { self.0.tag_name().name() == local_name } fn attribute_matches(&self, local_name: &str, operator: simplecss::AttributeOperator) -> bool { match self.0.attribute(local_name) { Some(value) => operator.matches(value), None => false, } } fn pseudo_class_matches(&self, class: simplecss::PseudoClass) -> bool { match class { simplecss::PseudoClass::FirstChild => self.prev_sibling_element().is_none(), _ => false, // Since we are querying a static XML we can ignore other pseudo-classes. } } } fn resolve_css<'a>(ro_doc: &'a roxmltree::Document<'a>) -> simplecss::StyleSheet<'a> { let mut sheet = simplecss::StyleSheet::new(); for node in ro_doc.descendants().filter(|n| n.has_tag_name("style")) { match node.attribute("type") { Some("text/css") => {} Some(_) => continue, None => {} } let style = match node.text() { Some(s) => s, None => continue, }; sheet.parse_more(style); } sheet } svgdom-0.18.0/src/parser/text.rs010064400017500001750000000211211352146051400147330ustar0000000000000000use std::str; use crate::{ AttributeId, AttributeValue, Document, Node, }; trait StrTrim { fn remove_first(&mut self); fn remove_last(&mut self); } impl StrTrim for String { fn remove_first(&mut self) { self.drain(0..1); } fn remove_last(&mut self) { self.pop(); } } #[derive(Clone, Copy, PartialEq, Debug)] enum XmlSpace { Default, Preserve, } // Prepare text nodes according to the spec: https://www.w3.org/TR/SVG11/text.html#WhiteSpace // // This function handles: // - 'xml:space' processing // - tabs and newlines removing/replacing // - spaces trimming pub fn prepare_text(doc: &mut Document) { // Remember nodes that have 'xml:space' changed. let mut nodes = Vec::new(); _prepare_text(&doc.root(), &mut nodes, XmlSpace::Default); // Remove temporary 'xml:space' attributes created during the text processing. for mut node in nodes { node.remove_attribute(AttributeId::Space); } let root = doc.root().clone(); doc.drain(root, |n| n.is_text() && n.text().is_empty()); } fn _prepare_text(parent: &Node, nodes: &mut Vec, parent_xmlspace: XmlSpace) { for mut node in parent.children().filter(|n| n.is_element()) { let xmlspace = get_xmlspace(&mut node, nodes, parent_xmlspace); if let Some(child) = node.first_child() { if child.is_text() { prepare_text_children(&node, nodes, xmlspace); continue; } } _prepare_text(&node, nodes, xmlspace); } } fn get_xmlspace(node: &mut Node, nodes: &mut Vec, default: XmlSpace) -> XmlSpace { { let attrs = node.attributes(); let v = attrs.get_value(AttributeId::Space); if let Some(&AttributeValue::String(ref s)) = v { return if s == "preserve" { XmlSpace::Preserve } else { XmlSpace::Default }; } } // 'xml:space' is not set - set it manually. set_xmlspace(node, nodes, default); default } fn set_xmlspace(node: &mut Node, nodes: &mut Vec, xmlspace: XmlSpace) { let xmlspace_str = match xmlspace { XmlSpace::Default => "default", XmlSpace::Preserve => "preserve", }; node.set_attribute((AttributeId::Space, xmlspace_str)); nodes.push(node.clone()); } fn prepare_text_children(parent: &Node, marked_nodes: &mut Vec, xmlspace: XmlSpace) { // Trim all descendant text nodes. for mut child in parent.descendants() { if child.is_text() { let child_xmlspace = get_xmlspace(&mut child.parent().unwrap(), marked_nodes, xmlspace); let new_text = { let text = child.text(); trim(text.as_ref(), child_xmlspace) }; child.set_text(&new_text); } } let mut nodes = Vec::new(); collect_text(parent, 0, &mut nodes); // `trim` method has already collapsed all spaces into a single one, // so we have to check only for one leading or trailing space. if nodes.len() == 1 { // Process element with a single text node child. let mut node = nodes[0].0.clone(); if xmlspace == XmlSpace::Default { let mut text = node.text_mut(); match text.len() { 0 => {} // An empty string. Do nothing. 1 => { // If string has only one character and it's a space - clear this string. if text.as_bytes()[0] == b' ' { text.clear(); } } _ => { // 'text' has at least 2 bytes, so indexing is safe. let c1 = text.as_bytes()[0]; let c2 = text.as_bytes()[text.len() - 1]; if c1 == b' ' { text.remove_first(); } if c2 == b' ' { text.remove_last(); } } } } else { // Do nothing when xml:space=preserve. } } else if nodes.len() > 1 { // Process element with many text node children. // We manage all text nodes as a single text node // and trying to remove duplicated spaces across nodes. // // For example 'Text text text' // is the same is 'Text text text' let mut i = 0; let len = nodes.len() - 1; let mut last_non_empty: Option = None; while i < len { // Process pairs. let (mut node1, depth1) = nodes[i].clone(); let (mut node2, depth2) = nodes[i + 1].clone(); if node1.text().is_empty() { if let Some(ref n) = last_non_empty { node1 = n.clone(); } } // Parent of the text node is always an element node and always exist, // so unwrap is safe. let xmlspace1 = get_xmlspace(&mut node1.parent().unwrap(), marked_nodes, xmlspace); let xmlspace2 = get_xmlspace(&mut node2.parent().unwrap(), marked_nodes, xmlspace); // >text<..>text< // 1 2 3 4 let (c1, c2, c3, c4) = { let text1 = node1.text(); let text2 = node2.text(); let bytes1 = text1.as_bytes(); let bytes2 = text2.as_bytes(); let c1 = bytes1.first().cloned(); let c2 = bytes1.last().cloned(); let c3 = bytes2.first().cloned(); let c4 = bytes2.last().cloned(); (c1, c2, c3, c4) }; // NOTE: xml:space processing is mostly an undefined behavior, // because everyone do this differently. // We mimic Chrome behavior. // Remove space from the second text node if both nodes has bound spaces. // From: 'Text text' // To: 'Text text' // // See text-tspan-02-b.svg for details. if c2 == Some(b' ') && c2 == c3 { if depth1 < depth2 { if xmlspace2 == XmlSpace::Default { node2.text_mut().remove_first(); } } else { if xmlspace1 == XmlSpace::Default && xmlspace2 == XmlSpace::Default { node1.text_mut().remove_last(); } else if xmlspace1 == XmlSpace::Preserve && xmlspace2 == XmlSpace::Default { node2.text_mut().remove_first(); } } } let is_first = i == 0; let is_last = i == len - 1; if is_first && c1 == Some(b' ') && xmlspace1 == XmlSpace::Default && !node1.text().is_empty() { // Remove leading space of the first text node. node1.text_mut().remove_first(); } else if is_last && c4 == Some(b' ') && !node2.text().is_empty() && xmlspace2 == XmlSpace::Default { // Remove trailing space of the last text node. // Also check that 'text2' is not empty already. node2.text_mut().remove_last(); } if is_last && c2 == Some(b' ') && !node1.text().is_empty() && node2.text().is_empty() && node1.text().ends_with(' ') { node1.text_mut().remove_last(); } if !node1.text().trim().is_empty() { last_non_empty = Some(node1.clone()); } i += 1; } } } fn collect_text(parent: &Node, depth: usize, nodes: &mut Vec<(Node, usize)>) { for child in parent.children() { if child.is_text() { nodes.push((child.clone(), depth)); } else if child.is_element() { collect_text(&child, depth + 1, nodes); } } } fn trim(text: &str, space: XmlSpace) -> String { let mut s = String::with_capacity(text.len()); let mut prev = '0'; for c in text.chars() { // \r, \n and \t should be converted into spaces. let c = match c { '\r' | '\n' | '\t' => ' ', _ => c, }; // Skip continuous spaces. if space == XmlSpace::Default && c == ' ' && c == prev { continue; } prev = c; s.push(c); } s } svgdom-0.18.0/src/tree.rs010064400017500001750000000545411347303220500134240ustar0000000000000000/*! rctree is a "DOM-like" tree implemented using reference counting. */ // This is a copy of the https://github.com/RazrFalcon/rctree // // Changes: // - Node::new marked as private // - Node::borrow marked as private // - Node::borrow_mut marked as private // - Node::make_copy removed // - Node::make_deep_copy removed use std::fmt; use std::cell::{RefCell, Ref, RefMut}; use std::rc::{Rc, Weak}; type Link = Rc>>; type WeakLink = Weak>>; /// A reference to a node holding a value of type `T`. Nodes form a tree. /// /// Internally, this uses reference counting for lifetime tracking /// and `std::cell::RefCell` for interior mutability. /// /// **Note:** Cloning a `Node` only increments a reference count. It does not copy the data. pub struct Node(Link); struct NodeData { root: Option>, parent: Option>, first_child: Option>, last_child: Option>, previous_sibling: Option>, next_sibling: Option>, data: T, } /// Cloning a `Node` only increments a reference count. It does not copy the data. impl Clone for Node { fn clone(&self) -> Self { Node(Rc::clone(&self.0)) } } impl PartialEq for Node { fn eq(&self, other: &Node) -> bool { Rc::ptr_eq(&self.0, &other.0) } } impl fmt::Debug for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&*self.borrow(), f) } } impl fmt::Display for Node { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&*self.borrow(), f) } } macro_rules! try_opt { ($expr: expr) => { match $expr { Some(value) => value, None => return None } } } impl Node { /// Creates a new node from its associated data. pub(crate) fn new(data: T) -> Node { Node(Rc::new(RefCell::new(NodeData { root: None, parent: None, first_child: None, last_child: None, previous_sibling: None, next_sibling: None, data, }))) } /// Returns a root node. /// /// If the current node is the root node - will return itself. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn root(&self) -> Node { match self.0.borrow().root.as_ref() { Some(v) => Node(v.upgrade().unwrap()), None => self.clone(), } } /// Returns a parent node, unless this node is the root of the tree. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn parent(&self) -> Option> { Some(Node(try_opt!(try_opt!(self.0.borrow().parent.as_ref()).upgrade()))) } /// Returns a first child of this node, unless it has no child. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn first_child(&self) -> Option> { Some(Node(try_opt!(self.0.borrow().first_child.as_ref()).clone())) } /// Returns a last child of this node, unless it has no child. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn last_child(&self) -> Option> { Some(Node(try_opt!(try_opt!(self.0.borrow().last_child.as_ref()).upgrade()))) } /// Returns the previous sibling of this node, unless it is a first child. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn previous_sibling(&self) -> Option> { Some(Node(try_opt!(try_opt!(self.0.borrow().previous_sibling.as_ref()).upgrade()))) } /// Returns the next sibling of this node, unless it is a last child. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn next_sibling(&self) -> Option> { Some(Node(try_opt!(self.0.borrow().next_sibling.as_ref()).clone())) } /// Returns a shared reference to this node's data /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub(crate) fn borrow(&self) -> Ref { Ref::map(self.0.borrow(), |v| &v.data) } /// Returns a unique/mutable reference to this node's data /// /// # Panics /// /// Panics if the node is currently borrowed. pub(crate) fn borrow_mut(&mut self) -> RefMut { RefMut::map(self.0.borrow_mut(), |v| &mut v.data) } /// Returns an iterator of nodes to this node and its ancestors. /// /// Includes the current node. pub fn ancestors(&self) -> Ancestors { Ancestors(Some(self.clone())) } /// Returns an iterator of nodes to this node and the siblings before it. /// /// Includes the current node. pub fn preceding_siblings(&self) -> PrecedingSiblings { PrecedingSiblings(Some(self.clone())) } /// Returns an iterator of nodes to this node and the siblings after it. /// /// Includes the current node. pub fn following_siblings(&self) -> FollowingSiblings { FollowingSiblings(Some(self.clone())) } /// Returns an iterator of nodes to this node's children. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn children(&self) -> Children { Children { next: self.first_child(), next_back: self.last_child(), } } /// Returns `true` if this node has children nodes. /// /// # Panics /// /// Panics if the node is currently mutably borrowed. pub fn has_children(&self) -> bool { self.first_child().is_some() } /// Returns an iterator of nodes to this node and its descendants, in tree order. /// /// Includes the current node. pub fn descendants(&self) -> Descendants { Descendants(self.traverse()) } /// Returns an iterator of nodes to this node and its descendants, in tree order. pub fn traverse(&self) -> Traverse { Traverse { root: self.clone(), next: Some(NodeEdge::Start(self.clone())), next_back: Some(NodeEdge::End(self.clone())), } } /// Detaches a node from its parent and siblings. Children are not affected. /// /// # Panics /// /// Panics if the node or one of its adjoining nodes is currently borrowed. pub fn detach(&mut self) { self.0.borrow_mut().detach(); } /// Appends a new child to this node, after existing children. /// /// # Panics /// /// Panics if the node, the new child, or one of their adjoining nodes is currently borrowed. pub fn append(&mut self, new_child: Node) { assert!(*self != new_child, "a node cannot be appended to itself"); let mut self_borrow = self.0.borrow_mut(); let mut last_child_opt = None; { let mut new_child_borrow = new_child.0.borrow_mut(); new_child_borrow.detach(); new_child_borrow.root = Some(self_borrow.root.clone().unwrap_or(Rc::downgrade(&self.0))); new_child_borrow.parent = Some(Rc::downgrade(&self.0)); if let Some(last_child_weak) = self_borrow.last_child.take() { if let Some(last_child_strong) = last_child_weak.upgrade() { new_child_borrow.previous_sibling = Some(last_child_weak); last_child_opt = Some(last_child_strong); } } self_borrow.last_child = Some(Rc::downgrade(&new_child.0)); } if let Some(last_child_strong) = last_child_opt { let mut last_child_borrow = last_child_strong.borrow_mut(); debug_assert!(last_child_borrow.next_sibling.is_none()); last_child_borrow.next_sibling = Some(new_child.0); } else { // No last child debug_assert!(self_borrow.first_child.is_none()); self_borrow.first_child = Some(new_child.0); } } /// Prepends a new child to this node, before existing children. /// /// # Panics /// /// Panics if the node, the new child, or one of their adjoining nodes is currently borrowed. pub fn prepend(&mut self, new_child: Node) { assert!(*self != new_child, "a node cannot be prepended to itself"); let mut self_borrow = self.0.borrow_mut(); { let mut new_child_borrow = new_child.0.borrow_mut(); new_child_borrow.detach(); new_child_borrow.root = Some(self_borrow.root.clone().unwrap_or(Rc::downgrade(&self.0))); new_child_borrow.parent = Some(Rc::downgrade(&self.0)); match self_borrow.first_child.take() { Some(first_child_strong) => { { let mut first_child_borrow = first_child_strong.borrow_mut(); debug_assert!(first_child_borrow.previous_sibling.is_none()); first_child_borrow.previous_sibling = Some(Rc::downgrade(&new_child.0)); } new_child_borrow.next_sibling = Some(first_child_strong); } None => { debug_assert!(self_borrow.first_child.is_none()); self_borrow.last_child = Some(Rc::downgrade(&new_child.0)); } } } self_borrow.first_child = Some(new_child.0); } /// Inserts a new sibling after this node. /// /// # Panics /// /// Panics if the node, the new sibling, or one of their adjoining nodes is currently borrowed. pub fn insert_after(&mut self, new_sibling: Node) { assert!(*self != new_sibling, "a node cannot be inserted after itself"); let mut self_borrow = self.0.borrow_mut(); { let mut new_sibling_borrow = new_sibling.0.borrow_mut(); new_sibling_borrow.detach(); new_sibling_borrow.root = self_borrow.root.clone(); new_sibling_borrow.parent = self_borrow.parent.clone(); new_sibling_borrow.previous_sibling = Some(Rc::downgrade(&self.0)); match self_borrow.next_sibling.take() { Some(next_sibling_strong) => { { let mut next_sibling_borrow = next_sibling_strong.borrow_mut(); debug_assert!({ let weak = next_sibling_borrow.previous_sibling.as_ref().unwrap(); Rc::ptr_eq(&weak.upgrade().unwrap(), &self.0) }); next_sibling_borrow.previous_sibling = Some(Rc::downgrade(&new_sibling.0)); } new_sibling_borrow.next_sibling = Some(next_sibling_strong); } None => { if let Some(parent_ref) = self_borrow.parent.as_ref() { if let Some(parent_strong) = parent_ref.upgrade() { let mut parent_borrow = parent_strong.borrow_mut(); parent_borrow.last_child = Some(Rc::downgrade(&new_sibling.0)); } } } } } self_borrow.next_sibling = Some(new_sibling.0); } /// Inserts a new sibling before this node. /// /// # Panics /// /// Panics if the node, the new sibling, or one of their adjoining nodes is currently borrowed. pub fn insert_before(&mut self, new_sibling: Node) { assert!(*self != new_sibling, "a node cannot be inserted before itself"); let mut self_borrow = self.0.borrow_mut(); let mut previous_sibling_opt = None; { let mut new_sibling_borrow = new_sibling.0.borrow_mut(); new_sibling_borrow.detach(); new_sibling_borrow.root = self_borrow.root.clone(); new_sibling_borrow.parent = self_borrow.parent.clone(); new_sibling_borrow.next_sibling = Some(self.0.clone()); if let Some(previous_sibling_weak) = self_borrow.previous_sibling.take() { if let Some(previous_sibling_strong) = previous_sibling_weak.upgrade() { new_sibling_borrow.previous_sibling = Some(previous_sibling_weak); previous_sibling_opt = Some(previous_sibling_strong); } } self_borrow.previous_sibling = Some(Rc::downgrade(&new_sibling.0)); } if let Some(previous_sibling_strong) = previous_sibling_opt { let mut previous_sibling_borrow = previous_sibling_strong.borrow_mut(); debug_assert!({ let rc = previous_sibling_borrow.next_sibling.as_ref().unwrap(); Rc::ptr_eq(rc, &self.0) }); previous_sibling_borrow.next_sibling = Some(new_sibling.0); } else { // No previous sibling. if let Some(parent_ref) = self_borrow.parent.as_ref() { if let Some(parent_strong) = parent_ref.upgrade() { let mut parent_borrow = parent_strong.borrow_mut(); parent_borrow.first_child = Some(new_sibling.0); } } } } } impl NodeData { /// Detaches a node from its parent and siblings. Children are not affected. fn detach(&mut self) { let parent_weak = self.parent.take(); let previous_sibling_weak = self.previous_sibling.take(); let next_sibling_strong = self.next_sibling.take(); let previous_sibling_opt = previous_sibling_weak.as_ref().and_then(|weak| weak.upgrade()); if let Some(next_sibling_ref) = next_sibling_strong.as_ref() { let mut next_sibling_borrow = next_sibling_ref.borrow_mut(); next_sibling_borrow.previous_sibling = previous_sibling_weak; } else if let Some(parent_ref) = parent_weak.as_ref() { if let Some(parent_strong) = parent_ref.upgrade() { let mut parent_borrow = parent_strong.borrow_mut(); parent_borrow.last_child = previous_sibling_weak; } } if let Some(previous_sibling_strong) = previous_sibling_opt { let mut previous_sibling_borrow = previous_sibling_strong.borrow_mut(); previous_sibling_borrow.next_sibling = next_sibling_strong; } else if let Some(parent_ref) = parent_weak.as_ref() { if let Some(parent_strong) = parent_ref.upgrade() { let mut parent_borrow = parent_strong.borrow_mut(); parent_borrow.first_child = next_sibling_strong; } } } } /// Iterators prelude. pub mod iterator { pub use super::Ancestors; pub use super::PrecedingSiblings; pub use super::FollowingSiblings; pub use super::Children; pub use super::Descendants; pub use super::Traverse; pub use super::NodeEdge; } macro_rules! impl_node_iterator { ($name: ident, $next: expr) => { impl Iterator for $name { type Item = Node; /// # Panics /// /// Panics if the node about to be yielded is currently mutably borrowed. fn next(&mut self) -> Option { match self.0.take() { Some(node) => { self.0 = $next(&node); Some(node) } None => None } } } } } /// An iterator of nodes to the ancestors a given node. pub struct Ancestors(Option>); impl_node_iterator!(Ancestors, |node: &Node| node.parent()); /// An iterator of nodes to the siblings before a given node. pub struct PrecedingSiblings(Option>); impl_node_iterator!(PrecedingSiblings, |node: &Node| node.previous_sibling()); /// An iterator of nodes to the siblings after a given node. pub struct FollowingSiblings(Option>); impl_node_iterator!(FollowingSiblings, |node: &Node| node.next_sibling()); /// A double ended iterator of nodes to the children of a given node. pub struct Children { next: Option>, next_back: Option>, } impl Children { // true if self.next_back's next sibling is self.next fn finished(&self) -> bool { match self.next_back { Some(ref next_back) => next_back.next_sibling() == self.next, _ => true, } } } impl Iterator for Children { type Item = Node; /// # Panics /// /// Panics if the node about to be yielded is currently mutably borrowed. fn next(&mut self) -> Option { if self.finished() { return None; } match self.next.take() { Some(node) => { self.next = node.next_sibling(); Some(node) } None => None } } } impl DoubleEndedIterator for Children { /// # Panics /// /// Panics if the node about to be yielded is currently mutably borrowed. fn next_back(&mut self) -> Option { if self.finished() { return None; } match self.next_back.take() { Some(node) => { self.next_back = node.previous_sibling(); Some(node) } None => None } } } /// An iterator of nodes to a given node and its descendants, in tree order. pub struct Descendants(Traverse); impl Iterator for Descendants { type Item = Node; /// # Panics /// /// Panics if the node about to be yielded is currently mutably borrowed. fn next(&mut self) -> Option { loop { match self.0.next() { Some(NodeEdge::Start(node)) => return Some(node), Some(NodeEdge::End(_)) => {} None => return None } } } } /// A node type during traverse. #[derive(Clone, Debug)] pub enum NodeEdge { /// Indicates that start of a node that has children. /// Yielded by `Traverse::next` before the node's descendants. /// In HTML or XML, this corresponds to an opening tag like `

` Start(Node), /// Indicates that end of a node that has children. /// Yielded by `Traverse::next` after the node's descendants. /// In HTML or XML, this corresponds to a closing tag like `
` End(Node), } // Implement PartialEq manually, because we do not need to require T: PartialEq impl PartialEq for NodeEdge { fn eq(&self, other: &NodeEdge) -> bool { match (&*self, &*other) { (&NodeEdge::Start(ref n1), &NodeEdge::Start(ref n2)) => *n1 == *n2, (&NodeEdge::End(ref n1), &NodeEdge::End(ref n2)) => *n1 == *n2, _ => false, } } } impl NodeEdge { fn next_item(&self, root: &Node) -> Option> { match *self { NodeEdge::Start(ref node) => match node.first_child() { Some(first_child) => Some(NodeEdge::Start(first_child)), None => Some(NodeEdge::End(node.clone())), }, NodeEdge::End(ref node) => { if *node == *root { None } else { match node.next_sibling() { Some(next_sibling) => Some(NodeEdge::Start(next_sibling)), None => match node.parent() { Some(parent) => Some(NodeEdge::End(parent)), // `node.parent()` here can only be `None` // if the tree has been modified during iteration, // but silently stoping iteration // seems a more sensible behavior than panicking. None => None, }, } } } } } fn previous_item(&self, root: &Node) -> Option> { match *self { NodeEdge::End(ref node) => match node.last_child() { Some(last_child) => Some(NodeEdge::End(last_child)), None => Some(NodeEdge::Start(node.clone())), }, NodeEdge::Start(ref node) => { if *node == *root { None } else { match node.previous_sibling() { Some(previous_sibling) => Some(NodeEdge::End(previous_sibling)), None => match node.parent() { Some(parent) => Some(NodeEdge::Start(parent)), // `node.parent()` here can only be `None` // if the tree has been modified during iteration, // but silently stoping iteration // seems a more sensible behavior than panicking. None => None } } } } } } } /// A double ended iterator of nodes to a given node and its descendants, /// in tree order. pub struct Traverse { root: Node, next: Option>, next_back: Option>, } impl Traverse { // true if self.next_back's next item is self.next fn finished(&self) -> bool { match self.next_back { Some(ref next_back) => next_back.next_item(&self.root) == self.next, _ => true, } } } impl Iterator for Traverse { type Item = NodeEdge; /// # Panics /// /// Panics if the node about to be yielded is currently mutably borrowed. fn next(&mut self) -> Option { if self.finished() { return None; } match self.next.take() { Some(item) => { self.next = item.next_item(&self.root); Some(item) } None => None } } } impl DoubleEndedIterator for Traverse { /// # Panics /// /// Panics if the node about to be yielded is currently mutably borrowed. fn next_back(&mut self) -> Option { if self.finished() { return None; } match self.next_back.take() { Some(item) => { self.next_back = item.previous_item(&self.root); Some(item) } None => None } } } svgdom-0.18.0/src/writer.rs010064400017500001750000000171061352165560400140050ustar0000000000000000use std::fmt; use log::warn; use xmlwriter::XmlWriter; use crate::{ AttributeId, AttributeValue, Document, ElementId, FilterSvgAttrs, Node, NodeData, NodeEdge, NodeType, QName, WriteBuffer, }; use crate::{ ListSeparator, ValueWriteOptions, }; pub use xmlwriter::Indent; /// Options that defines SVG writing. #[derive(Debug)] pub struct WriteOptions { /// Use single quote marks instead of double quote. /// /// # Examples /// /// Before: /// /// ```text /// /// ``` /// /// After: /// /// ```text /// /// ``` /// /// Default: disabled pub use_single_quote: bool, /// Set XML nodes indention. /// /// # Examples /// /// `Indent::None` /// /// Before: /// /// ```text /// /// /// /// /// ``` /// /// After: /// /// ```text /// /// ``` /// /// Default: 4 spaces pub indent: Indent, /// Set XML attributes indention. /// /// # Examples /// /// `Indent::Spaces(2)` /// /// Before: /// /// ```text /// /// /// /// /// ``` /// /// After: /// /// ```text /// /// /// /// ``` /// /// Default: `None` pub attributes_indent: Indent, /// `svgtypes` options. pub values: ValueWriteOptions, } impl Default for WriteOptions { fn default() -> Self { WriteOptions { indent: Indent::Spaces(4), attributes_indent: Indent::None, use_single_quote: false, values: ValueWriteOptions { trim_hex_colors: false, remove_leading_zero: false, use_compact_path_notation: false, join_arc_to_flags: false, remove_duplicated_path_commands: false, use_implicit_lineto_commands: false, simplify_transform_matrices: false, list_separator: ListSeparator::Space, }, } } } /// Writes a document into the buffer. pub(crate) fn write_dom(doc: &Document, opt: &WriteOptions) -> String { let xml_opt = xmlwriter::Options { use_single_quote: opt.use_single_quote, indent: opt.indent, attributes_indent: opt.attributes_indent, }; let mut xml = XmlWriter::new(xml_opt); for edge in doc.root().traverse() { match edge { NodeEdge::Start(node) => { match node.node_type() { NodeType::Root => {} NodeType::Element => { match *node.tag_name() { QName::Id(id) => xml.start_element(id.as_str()), QName::Name(ref s) => xml.start_element(s), } write_attributes(&node, opt, &mut xml); if node.has_tag_name(ElementId::Text) { xml.set_preserve_whitespaces(true); } } NodeType::Comment => { xml.write_comment(&node.text()); } NodeType::Text => { xml.write_text(&node.text()); } } } NodeEdge::End(node) => { if node.is_element() { xml.end_element(); } if node.has_tag_name(ElementId::Text) { xml.set_preserve_whitespaces(false); } } } } xml.end_document() } /// Writes attributes. /// /// Order: /// - 'id' /// - sorted SVG attributes /// - unsorted non-SVG attributes fn write_attributes( node: &Node, opt: &WriteOptions, xml: &mut XmlWriter, ) { // Write root SVG node attributes. if node.has_tag_name(ElementId::Svg) { if node.parent().map(|v| v.is_root()) == Some(true) { xml.write_attribute("xmlns", "http://www.w3.org/2000/svg"); let xlink_needed = node.descendants().any(|n| n.has_attribute(AttributeId::Href)); if xlink_needed { xml.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); } } } if node.has_id() { xml.write_attribute("id", &node.id()) } let attrs = node.attributes(); // sort attributes let mut ids: Vec<_> = attrs.iter().svg() .map(|(aid, attr)| (aid, attr.name.as_ref())) .collect(); ids.sort_by_key(|&(x, _)| x as usize); for &(id, name) in &ids { let attr = attrs.get(name).unwrap(); let name = match id { AttributeId::Href => "xlink:href", AttributeId::Space => "xml:space", _ => id.as_str(), }; if id == AttributeId::Unicode { if let AttributeValue::String(ref s) = attr.value { xml.write_attribute_raw(name, |buf| write_escaped(s, buf)); } else { warn!("An invalid 'unicode' attribute value: {:?}.", attr.value); } } else { xml.write_attribute_raw(name, |buf| attr.value.write_buf_opt(&opt.values, buf)); } } // write non-SVG attributes for attr in attrs.iter() { if let QName::Name(ref name) = attr.name { xml.write_attribute_raw(name, |buf| attr.value.write_buf_opt(&opt.values, buf)); } } } fn write_escaped(unicode: &str, out: &mut Vec) { use std::io::Write; if unicode.starts_with("&#") { out.extend_from_slice(unicode.as_bytes()); } else { for c in unicode.chars() { out.extend_from_slice(b"&#x"); write!(out, "{:x}", c as u32).unwrap(); out.push(b';'); } } } impl fmt::Debug for NodeData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.node_type { NodeType::Root => write!(f, "Root()"), NodeType::Element => { write!(f, "Element({}", self.tag_name)?; write_element_content(self, f, true, true)?; write!(f, ")") } NodeType::Comment => write!(f, "Comment({})", self.text), NodeType::Text => write!(f, "Text({})", self.text), } } } impl fmt::Display for NodeData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.node_type { NodeType::Root => write!(f, ""), NodeType::Element => { write!(f, "<{}", self.tag_name)?; write_element_content(self, f, true, false)?; write!(f, ">") } NodeType::Comment => write!(f, "", self.text), NodeType::Text => write!(f, "{}", self.text), } } } fn write_element_content( node: &NodeData, f: &mut fmt::Formatter, space_before_attrs: bool, print_linked: bool, ) -> fmt::Result { if !node.id.is_empty() { write!(f, " id='{}'", node.id)?; } if !node.attributes.is_empty() { if space_before_attrs { write!(f, " ")?; } write!(f, "{}", node.attributes)?; } if print_linked && !node.linked_nodes.is_empty() { write!(f, "; linked-nodes:")?; for node in &node.linked_nodes { write!(f, " '{}'", *node.id())?; } } Ok(()) } svgdom-0.18.0/tests/css.rs010064400017500001750000000166111352302217300136230ustar0000000000000000#[macro_use] extern crate pretty_assertions; use svgdom::{ Document, WriteOptions, }; fn write_options() -> WriteOptions { let mut opt = WriteOptions::default(); opt.use_single_quote = true; opt } macro_rules! test_resave { ($name:ident, $in_text:expr, $out_text:expr) => ( #[test] fn $name() { let doc = Document::from_str($in_text).unwrap(); assert_eq!(doc.to_string_with_opt(&write_options()), $out_text); } ) } test_resave!(parse_css_1, " ", " "); // style can be set after usage test_resave!(parse_css_2, " ", " "); test_resave!(parse_css_4, " ", " "); // empty data test_resave!(parse_css_5, " ", " "); // multiline comments and styles test_resave!(parse_css_6, " ", " "); // links should be properly linked test_resave!(parse_css_7, " ", " "); // order of styles ungrouping is important test_resave!(parse_css_8, " ", " "); // order of styles ungrouping is important test_resave!(parse_css_9, " ", " "); // style can be set without CDATA block test_resave!(parse_css_10, " ", " "); test_resave!(parse_css_11, " ", " "); test_resave!(parse_css_12, " ", " "); test_resave!(parse_css_14, " ", " "); test_resave!(parse_css_15, " ", " "); test_resave!(parse_css_16, " ", " "); // empty style test_resave!(parse_css_17, " ", " "); test_resave!(parse_css_18, " ", " "); test_resave!(parse_css_19, " ", " "); test_resave!(parse_css_20, " ", " "); test_resave!(parse_css_21, " ", " "); // marker property test_resave!(parse_css_23, " ", " "); // no `type` test_resave!(parse_css_24, " ", " "); // Skip non-presentation attributes, but keep transform. test_resave!(parse_css_25, " ", " "); svgdom-0.18.0/tests/debug.rs010064400017500001750000000051761351363065200141330ustar0000000000000000#[macro_use] extern crate pretty_assertions; use svgdom::{ AttributeId as AId, Document, ElementId as EId, NodeType, }; #[test] fn elem_1() { let mut doc = Document::new(); let svg_elem = doc.create_element(EId::Svg); assert_eq!(format!("{:?}", svg_elem), "Element(svg)"); assert_eq!(format!("{}", svg_elem), ""); } #[test] fn elem_2() { let mut doc = Document::new(); let mut svg_elem = doc.create_element(EId::Svg); svg_elem.set_attribute((AId::X, 1)); assert_eq!(format!("{:?}", svg_elem), "Element(svg x='1')"); assert_eq!(format!("{}", svg_elem), ""); } #[test] fn elem_3() { let mut doc = Document::new(); let mut svg_elem = doc.create_element(EId::Svg); svg_elem.set_id("svg1"); svg_elem.set_attribute((AId::X, 1)); svg_elem.set_attribute((AId::Y, 2)); assert_eq!(format!("{:?}", svg_elem), "Element(svg id='svg1' x='1' y='2')"); assert_eq!(format!("{}", svg_elem), ""); } #[test] fn elem_4() { let mut doc = Document::new(); let mut svg_elem = doc.create_element(EId::Svg); svg_elem.set_id("svg1"); svg_elem.set_attribute((AId::X, 1)); let mut lg_elem = doc.create_element(EId::LinearGradient); lg_elem.set_id("lg1"); svg_elem.set_attribute((AId::Fill, lg_elem.clone())); assert_eq!(format!("{:?}", svg_elem), "Element(svg id='svg1' x='1' fill='url(#lg1)')"); assert_eq!(format!("{}", svg_elem), ""); assert_eq!(format!("{:?}", lg_elem), "Element(linearGradient id='lg1'; linked-nodes: 'svg1')"); assert_eq!(format!("{}", lg_elem), ""); } #[test] fn root_1() { let doc = Document::new(); assert_eq!(format!("{:?}", doc.root()), "Root()"); assert_eq!(format!("{}", doc.root()), ""); } #[test] fn comment_1() { let mut doc = Document::new(); let node = doc.create_node(NodeType::Comment, "comment"); assert_eq!(format!("{:?}", node), "Comment(comment)"); assert_eq!(format!("{}", node), ""); } #[test] fn text_1() { let mut doc = Document::new(); let node = doc.create_node(NodeType::Text, "text"); assert_eq!(format!("{:?}", node), "Text(text)"); assert_eq!(format!("{}", node), "text"); } #[test] fn attributes_1() { let mut doc = Document::new(); let mut svg_elem = doc.create_element(EId::Svg); svg_elem.set_id("svg1"); svg_elem.set_attribute((AId::X, 1)); svg_elem.set_attribute((AId::Y, 2)); assert_eq!(format!("{:?}", svg_elem.attributes()), "Attributes(x='1' y='2')"); assert_eq!(format!("{}", *svg_elem.attributes()), "x='1' y='2'"); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������svgdom-0.18.0/tests/domapi.rs�����������������������������������������������������������������������0100644�0001750�0001750�00000023270�13521655624�0014316�0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#[macro_use] extern crate pretty_assertions; use svgdom::{ AttributeId as AId, AttributeValue, Document, ElementId as EId, WriteOptions, }; #[test] fn linked_attributes_1() { let mut doc = Document::new(); let mut n1 = doc.create_element(EId::Svg); let mut n2 = doc.create_element(EId::Svg); doc.root().append(n1.clone()); doc.root().append(n2.clone()); n2.set_id("2"); n1.set_attribute((AId::Href, n2.clone())); assert_eq!(n1.is_used(), false); assert_eq!(n2.is_used(), true); assert_eq!(*n2.linked_nodes().iter().next().unwrap(), n1); } #[test] fn linked_attributes_2() { let mut doc = Document::new(); let mut n1 = doc.create_element(EId::Svg); let mut n2 = doc.create_element(EId::Svg); n1.set_id("1"); n2.set_id("2"); doc.root().append(n1.clone()); doc.root().append(n2.clone()); n1.set_attribute((AId::Href, n2.clone())); // recursion error assert_eq!(n2.set_attribute_checked((AId::Href, n1.clone())).unwrap_err().to_string(), "element crosslink"); } #[test] fn linked_attributes_3() { let mut doc = Document::new(); { let mut n1 = doc.create_element(EId::Svg); let mut n2 = doc.create_element(EId::Svg); doc.root().append(n1.clone()); doc.root().append(n2.clone()); n1.set_id("1"); n2.set_id("2"); n1.set_attribute((AId::Href, n2.clone())); assert_eq!(n1.is_used(), false); assert_eq!(n2.is_used(), true); } { // remove n1 let n = doc.root().descendants().skip(1).next().unwrap(); assert_eq!(*n.id(), "1"); doc.remove_node(n); } { // n2 should became unused let n = doc.root().descendants().skip(1).next().unwrap(); assert_eq!(*n.id(), "2"); assert_eq!(n.is_used(), false); } } #[test] fn linked_attributes_4() { let mut doc = Document::new(); { let mut n1 = doc.create_element(EId::Svg); let mut n2 = doc.create_element(EId::Svg); doc.root().append(n1.clone()); doc.root().append(n2.clone()); n1.set_id("1"); n2.set_id("2"); n1.set_attribute((AId::Href, n2.clone())); assert_eq!(n1.is_used(), false); assert_eq!(n2.is_used(), true); } { // remove n2 let n = doc.root().descendants().nth(1).unwrap(); doc.remove_node(n); } { // xlink:href attribute from n1 should be removed let n = doc.root().descendants().next().unwrap(); assert_eq!(n.has_attribute(AId::Href), false); } } #[test] fn linked_attributes_5() { let mut doc = Document::new(); let mut n1 = doc.create_element(EId::Svg); let mut n2 = doc.create_element(EId::Svg); doc.root().append(n1.clone()); doc.root().append(n2.clone()); n1.set_id("1"); n2.set_id("2"); // no matter how many times we insert/clone/link same node, // amount of linked nodes in n1 must be 1 n2.set_attribute((AId::Fill, n1.clone())); n2.set_attribute((AId::Fill, n1.clone())); n2.set_attribute((AId::Fill, n1.clone())); n2.set_attribute((AId::Fill, n1.clone())); assert_eq!(n1.is_used(), true); assert_eq!(n2.is_used(), false); assert_eq!(n1.uses_count(), 1); } #[test] fn linked_attributes_6() { // Linked nodes not added to the tree should not cause a memory leak. let mut doc = Document::new(); let mut n1 = doc.create_element(EId::Svg); let mut n2 = doc.create_element(EId::Svg); n1.set_id("1"); n2.set_id("2"); n2.set_attribute((AId::Fill, n1.clone())); } #[test] fn attributes_must_be_uniq() { let mut doc = Document::new(); let mut n = doc.create_element(EId::Svg); n.set_attribute((AId::Fill, "red")); n.set_attribute((AId::Fill, "green")); assert_eq!(n.attributes().get_value(AId::Fill).unwrap(), &AttributeValue::from("green")); assert_eq!(n.attributes().len(), 1); } #[test] fn attributes_compare_1() { let mut doc = Document::new(); let mut n = doc.create_element(EId::Svg); n.set_attribute((AId::StrokeWidth, 1.0)); assert_eq!(n.attributes().get_value(AId::StrokeWidth).unwrap(), &AttributeValue::from(1.0)); } #[test] fn attributes_exist_1() { let mut doc = Document::new(); let mut n = doc.create_element(EId::Svg); n.set_attribute((AId::StrokeWidth, 1.0)); assert_eq!(n.has_attribute(AId::StrokeWidth), true); } #[test] fn attributes_exist_2() { let mut doc = Document::new(); let mut n = doc.create_element(EId::Svg); n.set_attribute((AId::StrokeWidth, 1.0)); assert_eq!(n.attributes().iter().find(|ref attr| attr.has_id(AId::StrokeWidth)).is_some(), true); } #[test] fn remove_attribute_1() { let mut doc = Document::new(); let mut n = doc.create_element(EId::Svg); n.set_attribute((AId::StrokeWidth, 1.0)); assert_eq!(n.has_attribute(AId::StrokeWidth), true); n.remove_attribute(AId::StrokeWidth); assert_eq!(n.has_attribute(AId::StrokeWidth), false); } #[test] fn drain_1() { let mut doc = Document::from_str( " ").unwrap(); let root = doc.root().clone(); assert_eq!(doc.drain(root, |n| n.has_tag_name(EId::Rect)), 1); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), "\n"); } #[test] fn drain_2() { let mut doc = Document::from_str( " ").unwrap(); let root = doc.root().clone(); assert_eq!(doc.drain(root, |n| n.has_tag_name(EId::Path)), 1); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn drain_3() { let mut doc = Document::from_str( " ").unwrap(); let root = doc.root().clone(); assert_eq!(doc.drain(root, |n| n.has_tag_name(EId::G)), 1); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn drain_4() { let mut doc = Document::from_str( " ").unwrap(); let root = doc.root().clone(); assert_eq!(doc.drain(root, |n| n.has_tag_name(EId::Rect)), 3); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn deep_copy_1() { let mut doc = Document::from_str( " ").unwrap(); let mut svg = doc.svg_element().unwrap(); let g = doc.root().descendants().find(|n| n.has_tag_name(EId::G)).unwrap(); // simple copy svg.append(doc.copy_node_deep(g)); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn deep_copy_2() { let mut doc = Document::from_str( " ").unwrap(); let mut g = doc.root().descendants().find(|n| n.has_tag_name(EId::G)).unwrap(); // copy itself let g1 = doc.copy_node_deep(g.clone()); g.append(g1); let g2 = doc.copy_node_deep(g.clone()); g.append(g2); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn deep_copy_3() { let mut doc = Document::from_str( " ").unwrap(); let mut svg = doc.svg_element().unwrap(); let g = doc.root().descendants().find(|n| n.has_tag_name(EId::G)).unwrap(); // test attributes copying svg.append(doc.copy_node_deep(g)); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn set_attr_1() { use svgdom::Attribute; let mut doc = Document::new(); let mut rect = doc.create_element(EId::Rect); let mut rect2 = doc.create_element(EId::Rect); rect2.set_id("rect2"); rect.set_attribute((AId::X, 1.0)); assert_eq!(rect.attributes().get(AId::X).unwrap().to_string(), "x='1'"); rect.set_attribute(("attr", 1.0)); assert_eq!(rect.attributes().get("attr").unwrap().to_string(), "attr='1'"); let attr = Attribute::new(AId::Y, 1.0); rect.set_attribute(attr); assert_eq!(rect.attributes().get(AId::Y).unwrap().to_string(), "y='1'"); rect.set_attribute((AId::Href, rect2)); assert_eq!(rect.attributes().get(AId::Href).unwrap().to_string(), "xlink:href='#rect2'"); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������svgdom-0.18.0/tests/parser.rs�����������������������������������������������������������������������0100644�0001750�0001750�00000030500�13523022064�0014317�0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#[macro_use] extern crate pretty_assertions; use std::fmt; use svgdom::{ AttributeId as AId, AttributeValue, Document, ElementId as EId, TagNameRef, NodeType, WriteOptions, }; fn write_options() -> WriteOptions { let mut opt = WriteOptions::default(); opt.use_single_quote = true; opt } #[derive(Clone, Copy, PartialEq)] struct TStr<'a>(pub &'a str); impl<'a> fmt::Debug for TStr<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } macro_rules! test_resave { ($name:ident, $in_text:expr, $out_text:expr) => ( #[test] fn $name() { let doc = Document::from_str($in_text).unwrap(); assert_eq!(TStr($out_text), TStr(doc.to_string_with_opt(&write_options()).as_str())); } ) } #[test] fn parse_empty_1() { assert_eq!(Document::from_str("").err().unwrap().to_string(), "the document does not have a root node"); } #[test] fn parse_empty_2() { assert_eq!(Document::from_str("\n \t").err().unwrap().to_string(), "the document does not have a root node"); } #[test] fn parse_empty_3() { assert_eq!(Document::from_str("").err().unwrap().to_string(), "the document does not have an SVG element"); } #[test] fn parse_empty_4() { assert_eq!(Document::from_str("").err().unwrap().to_string(), "the document does not have a root node"); } #[test] fn parse_single_node_1() { let doc = Document::from_str("").unwrap(); let child = doc.root().first_child().unwrap(); assert_eq!(child.tag_name().as_ref(), TagNameRef::from(EId::Svg)); assert_eq!(doc.root().children().count(), 1); } #[test] fn parse_comment_1() { let doc = Document::from_str("").unwrap(); let child = doc.root().children().nth(1).unwrap(); assert_eq!(child.node_type(), NodeType::Comment); assert_eq!(*child.text(), "comment"); assert_eq!(doc.root().children().count(), 2); } #[test] fn parse_text_1() { let doc = Document::from_str("text").unwrap(); let child = doc.root().first_child().unwrap().first_child().unwrap(); assert_eq!(child.node_type(), NodeType::Text); assert_eq!(*child.text(), "text"); } #[test] fn parse_text_2() { let doc = Document::from_str("Somecomplextext").unwrap(); let mut nodes = doc.root().first_child().unwrap().descendants(); let svg_node = nodes.next().unwrap(); assert_eq!(svg_node.tag_name().as_ref(), TagNameRef::from(EId::Svg)); assert_eq!(svg_node.node_type(), NodeType::Element); let text_node = nodes.next().unwrap(); assert_eq!(text_node.tag_name().as_ref(), TagNameRef::from(EId::Text)); assert_eq!(text_node.node_type(), NodeType::Element); let text_data_node = nodes.next().unwrap(); assert_eq!(*text_data_node.text(), "Some"); assert_eq!(text_data_node.node_type(), NodeType::Text); let tspan_node = nodes.next().unwrap(); assert_eq!(tspan_node.tag_name().as_ref(), TagNameRef::from(EId::Tspan)); assert_eq!(tspan_node.node_type(), NodeType::Element); let text_data_node_2 = nodes.next().unwrap(); assert_eq!(*text_data_node_2.text(), "complex"); assert_eq!(text_data_node_2.node_type(), NodeType::Text); let text_data_node_3 = nodes.next().unwrap(); assert_eq!(*text_data_node_3.text(), "text"); assert_eq!(text_data_node_3.node_type(), NodeType::Text); } test_resave!(parse_non_svg_1, " ", " "); test_resave!(parse_non_svg_2, " ", " "); // style must be ungroupped after presentation attributes test_resave!(parse_style_1, " ", " "); // style must be ungroupped after presentation attributes test_resave!(parse_style_2, " ", " "); // style must be ungroupped after presentation attributes test_resave!(parse_style_3, " ", " "); // comments inside attribute are ignored test_resave!(parse_style_4, " ", " "); // all attributes must begin with a letter test_resave!(parse_style_5, " ", " "); test_resave!(parse_style_6, " ", " "); test_resave!(parse_style_7, " ", " "); // Skip non-presentation attributes, but keep transform. test_resave!(parse_style_8, " ", " "); #[test] fn parse_paint_1() { let doc = Document::from_str( " ").unwrap(); let child = doc.root().first_child().unwrap(); let rg = child.children().nth(0).unwrap(); let rect = child.children().nth(1).unwrap(); assert_eq!(rg.is_used(), true); assert_eq!(rect.attributes().get_value(AId::Fill).unwrap(), &AttributeValue::Paint(rg, None)); } #[test] fn parse_paint_2() { // reversed order let doc = Document::from_str( " ").unwrap(); let child = doc.root().first_child().unwrap(); let rect = child.children().nth(0).unwrap(); let rg = child.children().nth(1).unwrap(); assert_eq!(rg.is_used(), true); assert_eq!(rect.attributes().get_value(AId::Fill).unwrap(), &AttributeValue::Paint(rg, None)); } test_resave!(parse_paint_3, " ", " "); test_resave!(parse_paint_4, " ", " "); test_resave!(parse_paint_5, " ", " "); test_resave!(parse_paint_6, " ", " "); test_resave!(parse_paint_7, " ", " "); test_resave!(parse_invalid_path, " ", " "); #[test] fn parse_iri_1() { let doc = Document::from_str( " ").unwrap(); let svg_node = doc.root().first_child().unwrap(); let rect_node = svg_node.children().nth(0).unwrap(); let use_node = svg_node.children().nth(1).unwrap(); assert_eq!(rect_node.is_used(), true); assert_eq!(use_node.attributes().get_value(AId::Href).unwrap(), &AttributeValue::Link(rect_node)); } #[test] fn parse_iri_2() { let doc = Document::from_str( " ").unwrap(); let svg_node = doc.root().first_child().unwrap(); let use_node = svg_node.children().nth(0).unwrap(); assert_eq!(use_node.attributes().get_value(AId::Href).unwrap(), &AttributeValue::String("#r1".to_string())); } #[test] fn parse_func_iri_1() { let doc = Document::from_str( " ").unwrap(); let svg_node = doc.root().first_child().unwrap(); let filter_node = svg_node.children().nth(0).unwrap(); let rect_node = svg_node.children().nth(1).unwrap(); assert_eq!(filter_node.is_used(), true); assert_eq!(rect_node.attributes().get_value(AId::Filter).unwrap(), &AttributeValue::FuncLink(filter_node)); } #[test] fn parse_func_iri_2() { let doc = Document::from_str( " ").unwrap(); let svg_node = doc.root().first_child().unwrap(); let rect_node = svg_node.children().nth(0).unwrap(); assert_eq!(rect_node.attributes().get_value(AId::Filter).unwrap(), &AttributeValue::String("url(#f)".to_string())); } // ignore empty LengthList test_resave!(parse_empty_attribute_1, " ", " "); // ignore empty NumberList test_resave!(parse_empty_attribute_2, " ", " "); // ignore empty Transform test_resave!(parse_empty_attribute_3, " ", " "); test_resave!(parse_viewbox_1, "", " "); test_resave!(crosslink_1, " ", " "); test_resave!(crosslink_2, " ", " "); // Checks that deep recursion doesn't cause a memory leak. test_resave!(crosslink_3, " ", " "); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������svgdom-0.18.0/tests/text.rs�������������������������������������������������������������������������0100644�0001750�0001750�00000024674�13513640025�0014030�0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#[macro_use] extern crate pretty_assertions; use svgdom::{ Document, ElementId as EId, NodeType, WriteOptions, }; #[derive(Clone, Copy, PartialEq)] struct TStr<'a>(pub &'a str); impl<'a> std::fmt::Debug for TStr<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.0) } } macro_rules! test_resave { ($name:ident, $in_text:expr, $out_text:expr) => ( #[test] fn $name() { let doc = Document::from_str($in_text).unwrap(); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(TStr($out_text), TStr(&doc.to_string_with_opt(&opt))); } ) } #[test] fn text_content_1() { let doc = Document::from_str( " A link inside tspan for testing ").unwrap(); let text: String = doc.root().descendants().map(|n| n.text().to_owned()).collect(); assert_eq!(text, "A link inside tspan for testing"); } #[test] fn text_content_2() { let doc = Document::from_str( " Text1 Text2 Text3 ").unwrap(); let text: String = doc.root().descendants().map(|n| n.text().to_owned()).collect(); assert_eq!(text, "Text1 Text2 Text3"); } #[test] fn text_content_3() { let doc = Document::from_str( " Not all characters in the text have a specified rotation ").unwrap(); let text: String = doc.root().descendants().map(|n| n.text().to_owned()).collect(); assert_eq!(text, "Not all characters in the text have a specified rotation"); } #[test] fn text_content_4() { let doc = Document::from_str( " Text Text Text ").unwrap(); let text: String = doc.root().descendants().map(|n| n.text().to_owned()).collect(); assert_eq!(text, "Text Text Text"); } #[test] fn text_content_5() { let doc = Document::from_str( " 'Text' ").unwrap(); let text: String = doc.root().descendants().map(|n| n.text().to_owned()).collect(); assert_eq!(text, "'Text'"); } // Manually created text. #[test] fn text_1() { let mut doc = Document::new(); let mut svg = doc.create_element(EId::Svg); let text = doc.create_node(NodeType::Text, "text"); doc.root().append(svg.clone()); svg.append(text.clone()); assert_eq!(doc.to_string(), " text "); } // Text inside svg element. test_resave!(text_3, " text ", " text "); // Multiline text. test_resave!(text_4, " Line 1 Line 2 Line 3 ", " Line 1 Line 2 Line 3 "); // Multiline text with 'preserve'. test_resave!(text_5, " Line 1 Line 2 Line 3 ", " Line 1 Line 2 Line 3 "); // Test trimming. // Details: https://www.w3.org/TR/SVG11/text.html#WhiteSpace test_resave!(text_6, " \t \n \r \t text \t text t \t\n \t \n text \t text t \t \r\n\r\n ", " text text t text text t "); // Escape. test_resave!(text_7, " &<> ", " &<> "); test_resave!(text_8, " Text ", " Text "); test_resave!(text_9, " &Text& ", " &Text& "); test_resave!(text_10, " &@@& ", " &@@& "); // Text with children elements. // Spaces will be trimmed, but not all. test_resave!(text_tspan_1, " Some \t complex text \t ", " Some complex text "); // Text with tspan but without spaces. test_resave!(text_tspan_2, " Text ", " Text "); // Text with tspan with new lines. test_resave!(text_tspan_3, " Text Text Text ", " Text Text Text "); // Text with spaces inside a tspan. test_resave!(text_tspan_4, " Some long text ", " Some long text "); // Text with spaces outside a tspan. test_resave!(text_tspan_5, " Some long text ", " Some long text "); // Nested tspan. test_resave!(text_tspan_6, " Some not very long text ", " Some not very long text "); // Empty tspan. test_resave!(text_tspan_7, " ", " "); test_resave!(text_tspan_8, " Text ", " Text "); test_resave!(text_tspan_9, " text text text ", " text text text "); test_resave!(text_tspan_10, " Not all characters in the text have a specified rotation ", " Not all characters in the text \ have a specified rotation "); // Test xml:space. test_resave!(text_space_preserve_1, " Text ", " Text "); // Test xml:space inheritance. test_resave!(text_space_preserve_2, " Text ", " Text "); // Test mixed xml:space. test_resave!(text_space_preserve_3, " Text Text Text ", " Text Text Text "); test_resave!(text_space_preserve_4, " Text Text Text ", " Text Text Text "); test_resave!(text_space_preserve_5, " Text Text Text ", " Text Text Text "); test_resave!(text_space_preserve_6, " Text ", " Text "); // Test xml:space propagation test_resave!(text_space_preserve_7, " Text Text ", " Text Text "); ��������������������������������������������������������������������svgdom-0.18.0/tests/writer.rs�����������������������������������������������������������������������0100644�0001750�0001750�00000024743�13513643032�0014356�0����������������������������������������������������������������������������������������������������ustar�00����������������������������������������������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#[macro_use] extern crate pretty_assertions; use svgdom::{ AttributeId as AId, Color, Document, ElementId as EId, Indent, Length, LengthUnit, NodeType, Transform, ViewBox, WriteOptions, NumberList, LengthList, }; macro_rules! test_resave { ($name:ident, $in_text:expr, $out_text:expr) => ( #[test] fn $name() { let doc = Document::from_str($in_text).unwrap(); let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), $out_text); } ) } #[test] fn empty_doc_1() { assert_eq!(Document::new().to_string(), "\n"); } #[test] fn single_node_1() { let mut doc = Document::new(); let n = doc.create_element(EId::Svg); doc.root().append(n.clone()); assert_eq!(doc.to_string(), "\n"); } #[test] fn child_node_1() { let mut doc = Document::new(); let mut svg = doc.create_element(EId::Svg); let defs = doc.create_element(EId::Defs); doc.root().append(svg.clone()); svg.append(defs.clone()); assert_eq!(doc.to_string(), " "); } #[test] fn child_nodes_1() { let mut doc = Document::new(); let svg = doc.create_element(EId::Svg); doc.root().append(svg.clone()); let mut parent = svg; for n in 1..5 { let mut r = doc.create_element(EId::Rect); r.set_id(n.to_string()); parent.append(r.clone()); parent = r; } assert_eq!(doc.to_string(), " "); } #[test] fn links_1() { let mut doc = Document::new(); let mut svg_n = doc.create_element(EId::Svg); let mut use_n = doc.create_element(EId::Use); svg_n.set_id("svg1"); doc.root().append(svg_n.clone()); svg_n.append(use_n.clone()); use_n.set_attribute((AId::Href, svg_n)); assert_eq!(doc.to_string(), " "); } #[test] fn links_2() { let mut doc = Document::new(); let mut svg_n = doc.create_element(EId::Svg); let mut lg_n = doc.create_element(EId::LinearGradient); let mut rect_n = doc.create_element(EId::Rect); lg_n.set_id("lg1"); doc.root().append(svg_n.clone()); svg_n.append(lg_n.clone()); svg_n.append(rect_n.clone()); rect_n.set_attribute((AId::Fill, lg_n)); assert_eq!(doc.to_string(), " "); } #[test] fn attributes_types_1() { let mut doc = Document::new(); let mut svg = doc.create_element(EId::Svg); doc.root().append(svg.clone()); svg.set_attribute((AId::ViewBox, ViewBox::new(10.0, 20.0, 30.0, 40.0))); svg.set_attribute((AId::Version, "1.0")); svg.set_attribute((AId::Width, 1.5)); svg.set_attribute((AId::Height, Length::new(1.5, LengthUnit::Percent))); svg.set_attribute((AId::Fill, Color::white())); svg.set_attribute((AId::Transform, Transform::new(2.0, 0.0, 0.0, 3.0, 20.0, 30.0))); svg.set_attribute((AId::StdDeviation, NumberList(vec![1.5, 2.5]))); svg.set_attribute((AId::StrokeDasharray, LengthList(vec![ Length::new(1.5, LengthUnit::Mm), Length::new(2.5, LengthUnit::Mm), Length::new(3.5, LengthUnit::Mm), ]))); // TODO: add path let mut opt = WriteOptions::default(); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), "\n"); } #[test] fn comment_1() { let mut doc = Document::new(); let comm = doc.create_node(NodeType::Comment, "comment"); let svg = doc.create_element(EId::Svg); doc.root().append(comm); doc.root().append(svg); assert_eq!(doc.to_string(), "\n\n"); } test_resave!(cdata_1, " ", " "); test_resave!(cdata_2, " ", " "); test_resave!(cdata_3, " ", " "); test_resave!(cdata_4, " ", " "); #[test] fn indent_1() { // default indent is 4 let doc = Document::from_str( " ").unwrap(); assert_eq!(doc.to_string(), " "); } #[test] fn indent_2() { let doc = Document::from_str( " ").unwrap(); let mut opt = WriteOptions::default(); opt.indent = Indent::Spaces(2); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn indent_3() { let doc = Document::from_str( " ").unwrap(); let mut opt = WriteOptions::default(); opt.indent = Indent::Spaces(0); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn indent_4() { let doc = Document::from_str( " ").unwrap(); let mut opt = WriteOptions::default(); opt.indent = Indent::None; opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), ""); } #[test] fn indent_5() { let doc = Document::from_str( " ").unwrap(); let mut opt = WriteOptions::default(); opt.indent = Indent::Tabs; opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " \t \t\t \t "); } #[test] fn attrs_indent_1() { let doc = Document::from_str( " ").unwrap(); let mut opt = WriteOptions::default(); opt.attributes_indent = Indent::Spaces(3); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), " "); } #[test] fn single_quote_1() { let doc = Document::from_str("").unwrap(); let mut opt = WriteOptions::default(); opt.indent = Indent::None; opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), ""); } test_resave!(escape_1, "", " "); // Do not escape already escaped. test_resave!(escape_2, "", " "); // Escape attribute values according to the current quote type. #[test] fn escape_3() { let doc = Document::from_str( "").unwrap(); let mut opt = WriteOptions::default(); opt.indent = Indent::None; assert_eq!(doc.to_string_with_opt(&opt), ""); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), ""); } // Escape attribute values according to the current quote type. #[test] fn escape_4() { let doc = Document::from_str( "").unwrap(); let mut opt = WriteOptions::default(); opt.indent = Indent::None; assert_eq!(doc.to_string_with_opt(&opt), ""); opt.use_single_quote = true; assert_eq!(doc.to_string_with_opt(&opt), ""); } test_resave!(namespaces_1, "", " "); test_resave!(namespaces_2, "", " "); test_resave!(namespaces_3, "", " "); // Non-SVG element. test_resave!(namespaces_4, " ", " "); test_resave!(aspect_ratio_1, "", " "); test_resave!(non_svg_1, " ", " "); �����������������������������svgdom-0.18.0/.cargo_vcs_info.json������������������������������������������������������������������0000644�����������������00000000112�00000000000�0012452�0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "git": { "sha1": "5f8cc06f4de936f530755ba39c1cb544ce3f42c6" } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������svgdom-0.18.0/Cargo.lock����������������������������������������������������������������������������0000644�����������������00000024132�00000000000�0010435�0����������������������������������������������������������������������������������������������������ustar�00�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bencher" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ctor" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "fern" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "float-cmp" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" version = "0.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "output_vt100" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "pretty_assertions" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "ctor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "proc-macro2" version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "quote" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "redox_syscall" version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "roxmltree" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "simplecss" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "siphasher" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "svgdom" version = "0.18.0" dependencies = [ "bencher 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "roxmltree 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "simplecss 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "svgtypes" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "float-cmp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "syn" version = "0.15.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "time" version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "xmlparser" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "xmlwriter" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum bencher 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdb4953a096c551ce9ace855a604d702e6e62d77fac690575ae347571717f5" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" "checksum ctor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3b4c17619643c1252b5f690084b82639dd7fac141c57c8e77a00e0148132092c" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "29d26fa0f4d433d1956746e66ec10d6bf4d6c8b93cd39965cceea7f7cc78c7dd" "checksum float-cmp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17ae3a6394183450225f33d9419cbd627b3cfc831e14f3f1146d5bcaf984e00c" "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" "checksum roxmltree 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "53b0200cbfa8b3f6cfd6076592717d697a1ddc57cb2a8fbfd3d133c06011b579" "checksum simplecss 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "596554e63596d556a0dbd681416342ca61c75f1a45203201e7e77d3fa2fa9014" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" "checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ecec95f00fb0ff019153e64ea520f87d1409769db3e8f4db3ea588638a3e1cee" "checksum xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������