Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  serializer.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::match_byte;
use dtoa_short::Notation;
use std::fmt::{self, Write};
use std::str;

use super::Token;

/// Trait for things the can serialize themselves in CSS syntax.
pub trait ToCss {
    /// Serialize `self` in CSS syntax, writing to `dest`.
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
    where
        W: fmt::Write;

    /// Serialize `self` in CSS syntax and return a string.
    ///
    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
    #[inline]
    fn to_css_string(&self) -> String {
        let mut s = String::new();
        self.to_css(&mut s).unwrap();
        s
    }
}

#[inline]
fn write_numeric<W>(value: f32, int_value: Option<i32>, has_sign: bool, dest: &mut W) -> fmt::Result
where
    W: fmt::Write,
{
    // `value.value >= 0` is true for negative 0.
    if has_sign && value.is_sign_positive() {
        dest.write_str("+")?;
    }

    let notation = if value == 0.0 && value.is_sign_negative() {
        // Negative zero. Work around #20596.
        dest.write_str("-0")?;
        Notation {
            decimal_point: false,
            scientific: false,
        }
    } else {
        dtoa_short::write(dest, value)?
    };

    if int_value.is_none() && value.fract() == 0. && !notation.decimal_point && !notation.scientific
    {
        dest.write_str(".0")?;
    }
    Ok(())
}

impl<'a> ToCss for Token<'a> {
    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
    where
        W: fmt::Write,
    {
        match *self {
            Token::Ident(ref value) => serialize_identifier(value, dest)?,
            Token::AtKeyword(ref value) => {
                dest.write_str("@")?;
                serialize_identifier(value, dest)?;
            }
            Token::Hash(ref value) => {
                dest.write_str("#")?;
                serialize_name(value, dest)?;
            }
            Token::IDHash(ref value) => {
                dest.write_str("#")?;
                serialize_identifier(value, dest)?;
            }
            Token::QuotedString(ref value) => serialize_string(value, dest)?,
            Token::UnquotedUrl(ref value) => {
                dest.write_str("url(")?;
                serialize_unquoted_url(value, dest)?;
                dest.write_str(")")?;
            }
            Token::Delim(value) => dest.write_char(value)?,

            Token::Number {
                value,
                int_value,
                has_sign,
            } => write_numeric(value, int_value, has_sign, dest)?,
            Token::Percentage {
                unit_value,
                int_value,
                has_sign,
            } => {
                write_numeric(unit_value * 100., int_value, has_sign, dest)?;
                dest.write_str("%")?;
            }
            Token::Dimension {
                value,
                int_value,
                has_sign,
                ref unit,
            } => {
                write_numeric(value, int_value, has_sign, dest)?;
                // Disambiguate with scientific notation.
                let unit = &**unit;
                // TODO(emilio): This doesn't handle e.g. 100E1m, which gets us
                // an unit of "E1m"...
                if unit == "e" || unit == "E" || unit.starts_with("e-") || unit.starts_with("E-") {
                    dest.write_str("\\65 ")?;
                    serialize_name(&unit[1..], dest)?;
                } else {
                    serialize_identifier(unit, dest)?;
                }
            }

            Token::WhiteSpace(content) => dest.write_str(content)?,
            Token::Comment(content) => {
                dest.write_str("/*")?;
                dest.write_str(content)?;
                dest.write_str("*/")?
            }
            Token::Colon => dest.write_str(":")?,
            Token::Semicolon => dest.write_str(";")?,
            Token::Comma => dest.write_str(",")?,
            Token::IncludeMatch => dest.write_str("~=")?,
            Token::DashMatch => dest.write_str("|=")?,
            Token::PrefixMatch => dest.write_str("^=")?,
            Token::SuffixMatch => dest.write_str("$=")?,
            Token::SubstringMatch => dest.write_str("*=")?,
            Token::CDO => dest.write_str("<!--")?,
            Token::CDC => dest.write_str("-->")?,

            Token::Function(ref name) => {
                serialize_identifier(name, dest)?;
                dest.write_str("(")?;
            }
            Token::ParenthesisBlock => dest.write_str("(")?,
            Token::SquareBracketBlock => dest.write_str("[")?,
            Token::CurlyBracketBlock => dest.write_str("{")?,

            Token::BadUrl(ref contents) => {
                dest.write_str("url(")?;
                dest.write_str(contents)?;
                dest.write_char(')')?;
            }
            Token::BadString(ref value) => {
                // During tokenization, an unescaped newline after a quote causes
                // the token to be a BadString instead of a QuotedString.
                // The BadString token ends just before the newline
                // (which is in a separate WhiteSpace token),
                // and therefore does not have a closing quote.
                dest.write_char('"')?;
                CssStringWriter::new(dest).write_str(value)?;
            }
            Token::CloseParenthesis => dest.write_str(")")?,
            Token::CloseSquareBracket => dest.write_str("]")?,
            Token::CloseCurlyBracket => dest.write_str("}")?,
        }
        Ok(())
    }
}

fn hex_escape<W>(ascii_byte: u8, dest: &mut W) -> fmt::Result
where
    W: fmt::Write,
{
    static HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
    let b3;
    let b4;
    let bytes = if ascii_byte > 0x0F {
        let high = (ascii_byte >> 4) as usize;
        let low = (ascii_byte & 0x0F) as usize;
        b4 = [b'\\', HEX_DIGITS[high], HEX_DIGITS[low], b' '];
        &b4[..]
    } else {
        b3 = [b'\\', HEX_DIGITS[ascii_byte as usize], b' '];
        &b3[..]
    };
    dest.write_str(unsafe { str::from_utf8_unchecked(bytes) })
}

fn char_escape<W>(ascii_byte: u8, dest: &mut W) -> fmt::Result
where
    W: fmt::Write,
{
    let bytes = [b'\\', ascii_byte];
    dest.write_str(unsafe { str::from_utf8_unchecked(&bytes) })
}

/// Write a CSS identifier, escaping characters as necessary.
pub fn serialize_identifier<W>(mut value: &str, dest: &mut W) -> fmt::Result
where
    W: fmt::Write,
{
    if value.is_empty() {
        return Ok(());
    }

    if let Some(value) = value.strip_prefix("--") {
        dest.write_str("--")?;
        serialize_name(value, dest)
    } else if value == "-" {
        dest.write_str("\\-")
    } else {
        if value.as_bytes()[0] == b'-' {
            dest.write_str("-")?;
            value = &value[1..];
        }
        if let digit @ b'0'..=b'9' = value.as_bytes()[0] {
            hex_escape(digit, dest)?;
            value = &value[1..];
        }
        serialize_name(value, dest)
    }
}

/// Write a CSS name, like a custom property name.
///
/// You should only use this when you know what you're doing, when in doubt,
/// consider using `serialize_identifier`.
pub fn serialize_name<W>(value: &str, dest: &mut W) -> fmt::Result
where
    W: fmt::Write,
{
    let mut chunk_start = 0;
    for (i, b) in value.bytes().enumerate() {
        let escaped = match_byte! { b,
            b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'-' => continue,
            b'\0' => Some("\u{FFFD}"),
            b => {
                if !b.is_ascii() {
                    continue;
                }
                None
            },
        };
        dest.write_str(&value[chunk_start..i])?;
        if let Some(escaped) = escaped {
            dest.write_str(escaped)?;
        } else if (b'\x01'..=b'\x1F').contains(&b) || b == b'\x7F' {
            hex_escape(b, dest)?;
        } else {
            char_escape(b, dest)?;
        }
        chunk_start = i + 1;
    }
    dest.write_str(&value[chunk_start..])
}

fn serialize_unquoted_url<W>(value: &str, dest: &mut W) -> fmt::Result
where
    W: fmt::Write,
{
    let mut chunk_start = 0;
    for (i, b) in value.bytes().enumerate() {
        let hex = match_byte! { b,
            b'\0'..=b' ' | b'\x7F' => true,
            b'(' | b')' | b'"' | b'\'' | b'\\' => false,
            _ => continue,
        };
        dest.write_str(&value[chunk_start..i])?;
        if hex {
            hex_escape(b, dest)?;
        } else {
            char_escape(b, dest)?;
        }
        chunk_start = i + 1;
    }
    dest.write_str(&value[chunk_start..])
}

/// Write a double-quoted CSS string token, escaping content as necessary.
pub fn serialize_string<W>(value: &str, dest: &mut W) -> fmt::Result
where
    W: fmt::Write,
{
    dest.write_str("\"")?;
    CssStringWriter::new(dest).write_str(value)?;
    dest.write_str("\"")?;
    Ok(())
}

/// A `fmt::Write` adapter that escapes text for writing as a double-quoted CSS string.
/// Quotes are not included.
///
/// Typical usage:
///
/// ```{rust,ignore}
/// fn write_foo<W>(foo: &Foo, dest: &mut W) -> fmt::Result where W: fmt::Write {
///     dest.write_str("\"")?;
///     {
///         let mut string_dest = CssStringWriter::new(dest);
///         // Write into string_dest...
///     }
///     dest.write_str("\"")?;
///     Ok(())
/// }
/// ```
pub struct CssStringWriter<'a, W> {
    inner: &'a mut W,
}

impl<'a, W> CssStringWriter<'a, W>
where
    W: fmt::Write,
{
    /// Wrap a text writer to create a `CssStringWriter`.
    pub fn new(inner: &'a mut W) -> CssStringWriter<'a, W> {
        CssStringWriter { inner }
    }
}

impl<'a, W> fmt::Write for CssStringWriter<'a, W>
where
    W: fmt::Write,
{
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let mut chunk_start = 0;
        for (i, b) in s.bytes().enumerate() {
            let escaped = match_byte! { b,
                b'"' => Some("\\\""),
                b'\\' => Some("\\\\"),
                b'\0' => Some("\u{FFFD}"),
                b'\x01'..=b'\x1F' | b'\x7F' => None,
                _ => continue,
            };
            self.inner.write_str(&s[chunk_start..i])?;
            match escaped {
                Some(x) => self.inner.write_str(x)?,
                None => hex_escape(b, self.inner)?,
            };
            chunk_start = i + 1;
        }
        self.inner.write_str(&s[chunk_start..])
    }
}

macro_rules! impl_tocss_for_int {
    ($T: ty) => {
        impl ToCss for $T {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
            where
                W: fmt::Write,
            {
                let mut buf = itoa::Buffer::new();
                dest.write_str(buf.format(*self))
            }
        }
    };
}

impl_tocss_for_int!(i8);
impl_tocss_for_int!(u8);
impl_tocss_for_int!(i16);
impl_tocss_for_int!(u16);
impl_tocss_for_int!(i32);
impl_tocss_for_int!(u32);
impl_tocss_for_int!(i64);
impl_tocss_for_int!(u64);

macro_rules! impl_tocss_for_float {
    ($T: ty) => {
        impl ToCss for $T {
            fn to_css<W>(&self, dest: &mut W) -> fmt::Result
            where
                W: fmt::Write,
            {
                dtoa_short::write(dest, *self).map(|_| ())
            }
        }
    };
}

impl_tocss_for_float!(f32);
impl_tocss_for_float!(f64);

/// A category of token. See the `needs_separator_when_before` method.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub enum TokenSerializationType {
    /// No token serialization type.
    #[default]
    Nothing,

    /// The [`<whitespace-token>`](https://drafts.csswg.org/css-syntax/#whitespace-token-diagram)
    /// type.
    WhiteSpace,

    /// The [`<at-keyword-token>`](https://drafts.csswg.org/css-syntax/#at-keyword-token-diagram)
    /// type, the "[`<hash-token>`](https://drafts.csswg.org/css-syntax/#hash-token-diagram) with
    /// the type flag set to 'unrestricted'" type, or the
    /// "[`<hash-token>`](https://drafts.csswg.org/css-syntax/#hash-token-diagram) with the type
    /// flag set to 'id'" type.
    AtKeywordOrHash,

    /// The [`<number-token>`](https://drafts.csswg.org/css-syntax/#number-token-diagram) type.
    Number,

    /// The [`<dimension-token>`](https://drafts.csswg.org/css-syntax/#dimension-token-diagram)
    /// type.
    Dimension,

    /// The [`<percentage-token>`](https://drafts.csswg.org/css-syntax/#percentage-token-diagram)
    /// type.
    Percentage,

    /// The [`<url-token>`](https://drafts.csswg.org/css-syntax/#url-token-diagram) or
    /// `<bad-url-token>` type.
    UrlOrBadUrl,

    /// The [`<function-token>`](https://drafts.csswg.org/css-syntax/#function-token-diagram) type.
    Function,

    /// The [`<ident-token>`](https://drafts.csswg.org/css-syntax/#ident-token-diagram) type.
    Ident,

    /// The `-->` [`<CDC-token>`](https://drafts.csswg.org/css-syntax/#CDC-token-diagram) type.
    CDC,

    /// The `|=`
    /// [`<dash-match-token>`](https://drafts.csswg.org/css-syntax/#dash-match-token-diagram) type.
    DashMatch,

    /// The `*=`
    /// [`<substring-match-token>`](https://drafts.csswg.org/css-syntax/#substring-match-token-diagram)
    /// type.
    SubstringMatch,

    /// The `<(-token>` type.
    OpenParen,

    /// The `#` `<delim-token>` type.
    DelimHash,

    /// The `@` `<delim-token>` type.
    DelimAt,

    /// The `.` or `+` `<delim-token>` type.
    DelimDotOrPlus,

    /// The `-` `<delim-token>` type.
    DelimMinus,

    /// The `?` `<delim-token>` type.
    DelimQuestion,

    /// The `$`, `^`, or `~` `<delim-token>` type.
    DelimAssorted,

    /// The `=` `<delim-token>` type.
    DelimEquals,

    /// The `|` `<delim-token>` type.
    DelimBar,

    /// The `/` `<delim-token>` type.
    DelimSlash,

    /// The `*` `<delim-token>` type.
    DelimAsterisk,

    /// The `%` `<delim-token>` type.
    DelimPercent,

    /// A type indicating any other token.
    Other,
}

impl TokenSerializationType {
    /// Return a value that represents the absence of a token, e.g. before the start of the input.
    #[deprecated(
        since = "0.32.1",
        note = "use TokenSerializationType::Nothing or TokenSerializationType::default() instead"
    )]
    pub fn nothing() -> TokenSerializationType {
        Default::default()
    }

    /// If this value is `TokenSerializationType::Nothing`, set it to the given value instead.
    pub fn set_if_nothing(&mut self, new_value: TokenSerializationType) {
        if matches!(self, TokenSerializationType::Nothing) {
            *self = new_value
        }
    }

    /// Return true if, when a token of category `self` is serialized just before
    /// a token of category `other` with no whitespace in between,
    /// an empty comment `/**/` needs to be inserted between them
    /// so that they are not re-parsed as a single token.
    ///
    /// See https://drafts.csswg.org/css-syntax/#serialization
    ///
    /// See https://github.com/w3c/csswg-drafts/issues/4088 for the
    /// `DelimPercent` bits.
    pub fn needs_separator_when_before(self, other: TokenSerializationType) -> bool {
        use self::TokenSerializationType::*;
        match self {
            Ident => matches!(
                other,
                Ident
                    | Function
                    | UrlOrBadUrl
                    | DelimMinus
                    | Number
                    | Percentage
                    | Dimension
                    | CDC
                    | OpenParen
            ),
            AtKeywordOrHash | Dimension => matches!(
                other,
                Ident | Function | UrlOrBadUrl | DelimMinus | Number | Percentage | Dimension | CDC
            ),
            DelimHash | DelimMinus => matches!(
                other,
                Ident | Function | UrlOrBadUrl | DelimMinus | Number | Percentage | Dimension
            ),
            Number => matches!(
                other,
                Ident
                    | Function
                    | UrlOrBadUrl
                    | DelimMinus
                    | Number
                    | Percentage
                    | DelimPercent
                    | Dimension
            ),
            DelimAt => matches!(other, Ident | Function | UrlOrBadUrl | DelimMinus),
            DelimDotOrPlus => matches!(other, Number | Percentage | Dimension),
            DelimAssorted | DelimAsterisk => matches!(other, DelimEquals),
            DelimBar => matches!(other, DelimEquals | DelimBar | DashMatch),
            DelimSlash => matches!(other, DelimAsterisk | SubstringMatch),
            Nothing | WhiteSpace | Percentage | UrlOrBadUrl | Function | CDC | OpenParen
            | DashMatch | SubstringMatch | DelimQuestion | DelimEquals | DelimPercent | Other => {
                false
            }
        }
    }
}

impl<'a> Token<'a> {
    /// Categorize a token into a type that determines when `/**/` needs to be inserted
    /// between two tokens when serialized next to each other without whitespace in between.
    ///
    /// See the `TokenSerializationType::needs_separator_when_before` method.
    pub fn serialization_type(&self) -> TokenSerializationType {
        use self::TokenSerializationType::*;
        match self {
            Token::Ident(_) => Ident,
            Token::AtKeyword(_) | Token::Hash(_) | Token::IDHash(_) => AtKeywordOrHash,
            Token::UnquotedUrl(_) | Token::BadUrl(_) => UrlOrBadUrl,
            Token::Delim('#') => DelimHash,
            Token::Delim('@') => DelimAt,
            Token::Delim('.') | Token::Delim('+') => DelimDotOrPlus,
            Token::Delim('-') => DelimMinus,
            Token::Delim('?') => DelimQuestion,
            Token::Delim('$') | Token::Delim('^') | Token::Delim('~') => DelimAssorted,
            Token::Delim('%') => DelimPercent,
            Token::Delim('=') => DelimEquals,
            Token::Delim('|') => DelimBar,
            Token::Delim('/') => DelimSlash,
            Token::Delim('*') => DelimAsterisk,
            Token::Number { .. } => Number,
            Token::Percentage { .. } => Percentage,
            Token::Dimension { .. } => Dimension,
            Token::WhiteSpace(_) => WhiteSpace,
            Token::Comment(_) => DelimSlash,
            Token::DashMatch => DashMatch,
            Token::SubstringMatch => SubstringMatch,
            Token::CDC => CDC,
            Token::Function(_) => Function,
            Token::ParenthesisBlock => OpenParen,
            Token::SquareBracketBlock
            | Token::CurlyBracketBlock
            | Token::CloseParenthesis
            | Token::CloseSquareBracket
            | Token::CloseCurlyBracket
            | Token::QuotedString(_)
            | Token::BadString(_)
            | Token::Delim(_)
            | Token::Colon
            | Token::Semicolon
            | Token::Comma
            | Token::CDO
            | Token::IncludeMatch
            | Token::PrefixMatch
            | Token::SuffixMatch => Other,
        }
    }
}

[ Dauer der Verarbeitung: 0.32 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge