Skip to content

v0.4.0

Choose a tag to compare

@sjp sjp released this 04 Sep 03:47
· 25 commits to master since this release

Added

  • :lang() under Mode::Html and Mode::Xhtml now accepts a * subtag in any
    position, which is what Selectors 4's RFC 4647 extended filtering allows:
    :lang(*-CH) — "any language as written in Switzerland" — was rejected as "a
    wildcard outside the final subtag", and now matches de-CH and fr-Latn-CH.
    A leading * stands for the tag's first subtag, so the walk starts one
    subtag in rather than anchoring with starts-with, and :lang(*-CH)
    therefore does not match the tag ch itself. A * anywhere else moves past
    nothing — every range subtag after the first is already searched for through
    the whole remaining tag — so it drops out of the translation: :lang(de-*-DE)
    emits exactly what :lang(de-DE) emits, as :lang(en-*) already emitted what
    :lang(en) does. Mode::Generic still takes only * and a final en-*,
    since it hands the range to XPath's lang(), a prefix match with nowhere to
    put the rest; its error now says so.

Changed

  • An option or optgroup inside a disabled select is now :disabled rather
    than :enabled, and an option is now disabled by the nearest optgroup
    above it rather than only by a parent one. Both are rules HTML added to
    "actually disabled" after this translation was written: an optgroup or
    option "whose nearest ancestor select is disabled" is actually disabled,
    and an option is disabled by the nearest ancestor that settles it — the
    walk ends at a select, hr, datalist or option, so an option in a
    <datalist> inside a disabled select stays :enabled, and an option
    nested below its optgroup rather than directly under it is disabled. This
    changes what :disabled, :enabled, option:disabled, option:enabled,
    optgroup:disabled and optgroup:enabled select over a document with a
    disabled select in it, which is the ordinary way a whole control is
    switched off, as well as the expressions they translate to. One corner is
    approximated, as the legend counting already is: HTML's walk also gives up
    once it has passed a second optgroup, which XPath cannot count, so an
    option two optgroups deep — non-conforming markup — inside a disabled
    select is :disabled here.
  • The error for a namespace prefix that is not an XML NCName now names the
    rule rather than an operation the prefix cannot have: \31 ns|div gives
    a namespace prefix that is not an XPath name (`1ns`) where it
    previously gave "a namespace prefix that needs quoting". Quoting is what a
    local name that cannot be a node test gets — it folds into a name() or
    local-name() comparison — so the old wording named the very fallback the
    prefix is being refused for. The README now records why that fallback stops
    at the prefix: it is an exact rewrite of a node test, whereas comparing a
    whole prefix:name against name() (which some other translators emit
    here) would match on how the document spells its prefix, where every
    prefix this crate emits is resolved by what the caller bound it to — and
    nothing the caller binds could help, since an XPath expression cannot name
    such a prefix at all. Which selectors translate is unchanged.
  • The Mode::Generic error for a wildcard it cannot place now names the reason
    rather than only the placement: the :lang() language range "*-CH" (a wildcard outside the final subtag, which XPath's lang() cannot express).
    The range is no longer an error everywhere, so the message has to say which
    translator it is beyond.
  • The caret in a parse error's message now points at the token the message
    names, rather than at the position the parse stopped on. [a=b c] reports
    unexpected `c` at byte 5, the c, where it previously reported byte 4,
    the space in front of it; :nth-child(foo) reports unexpected `foo` at
    byte 11 rather than byte 14, the ) past it; and a::part(b) reports
    `part` is not a supported pseudo-class or pseudo-element at byte 3, the
    name, rather than byte 8, the argument. Error::Parse's offset is
    therefore usable as a span: where the message echoes a piece of the selector,
    the offset is where that piece was written. Positions already on what they
    named — div.-5, reported on the -5 rather than on the . — do not move.
  • A selector group whose first token cannot start a compound is now reported by
    that token rather than as ParseErrorKind::EmptySelector: #1abc gives
    unexpected `#1abc` where it previously gave "the selector is empty", which
    sent the reader looking for a missing selector instead of at the
    digit-leading identifier that is there. It is also the message p#1abc has
    always given, so the same mistake no longer reports two ways depending on
    whether a type selector precedes it. EmptySelector now means only what it
    says: "", " ", "a, , b".
  • The README listed :link/:any-link as matching an a or area with an
    href without saying that the link element, which also carries an href,
    is outside that set — the difference a reader hits first, since other
    translators (selectr) do match it. It now says so, and the exclusion is
    quoted from HTML's own wording ("all a elements that have an href
    attribute, and all area elements that have an href attribute, must match
    one of :link and :visited") where the translation makes it, so the decision
    is not re-opened from memory. Both HTML fixtures grew a <link href> in a
    head, pinning at the document level what link:link → link[0] already
    pinned as a string. No behaviour changed.
  • The README and the code left the :disabled/:enabled element set open to
    being read as type-sensitive, since it sits beside :required,
    :read-write and :placeholder-shown, where an input's type does decide.
    It is not: HTML's disabled attribute has no "Applies to" list of type
    states, so <input type="hidden" disabled> is :disabled, and neither half
    of the pair emits a @type test at all. Other translators (selectr) drop
    Hidden from both halves, a carve-out the spec does not make. The README now
    says the set is by element alone, the constant holding it says why, and
    tests/html_mode.rs asserts the absence of the @type test rather than
    leaving it to be noticed. Both HTML fixtures grew a disabled hidden input
    and the differential fixture a disabled Hidden/RANGE pair, so a document
    pins it too. No behaviour changed.
  • [attr$=value] now spaces the subtraction in the expression it translates
    to: a[href$="pdf"] gives substring(@href, string-length(@href) - 2) = 'pdf' where it previously gave string-length(@href)-2. The two parse
    identically as XPath, so only the string differs — but the string is the
    contract. This was the crate's last unspaced binary operator; every other one
    it emits already carried a space on both sides. selectr 0.7-0 made the same
    change, so the two remain comparable string-for-string.