golang.org/x/net/html.ParseFragmentWithOptions initializes its form
pointer based on whether the context element or any of its ancestors
is a form element. If the context has a form ancestor, nested form tags
are discarded per HTML parsing rules.
Previously, cachedParseHtmlWithContext keyed cached fragments strictly
by context tag name. When a selection contained targets both inside and
outside forms sharing the same tag name, the first visited target cached
its parsed fragment, leading to incorrect parsing for subsequent targets
under the other form-ancestor state.
Differentiate the fragment cache key by appending a form-ancestor
indicator when context or any of its ancestors is a form element.
Fixes#614
Cover Add/Remove/ToggleClass over class names containing NBSP, EM SPACE,
NARROW NBSP and vertical tab, mid-name and at attribute edges, plus
direct tokenizer expectations that only the five ASCII whitespace
characters split tokens. Attribute round-trips verify the stored value
byte-for-byte. These cases fail on master after #552 and pass with the
preceding change.
Signed-off-by: sergioperezcheco <checo520@outlook.com>
HTML class tokens are separated by ASCII whitespace only (WHATWG HTML
2.3.7); any other character is part of the token. #552 switched
getClassesSlice to strings.Fields, which also splits on Unicode
whitespace, so a single class name containing NBSP/EM SPACE/NARROW NBSP
was read as several tokens by Add/Remove/ToggleClass while HasClass and
the class-attribute normalization treat those characters as token
content. The result: AddClass re-appends the split pieces as new classes,
RemoveClass and ToggleClass cannot remove the name, and setClasses
trimmed Unicode whitespace from attribute edges.
Restore ASCII-only boundaries: getClassesSlice uses FieldsFunc over the
five ASCII whitespace characters, and the entry/exit trimming in the
helpers and setClasses trims the same set so Unicode whitespace at
attribute edges is preserved verbatim. Form feed stays a separator, and
the tokenizer change keeps the no-empty-entries behavior of #552.
Benchmarks (darwin/arm64, Go 1.26.4, -count=3 means vs master):
AddClass 27279 -> 28776 ns/op (+5.5%), RemoveClass 28166 -> 29047
ns/op (+3.1%); B/op and allocs/op unchanged (7024/1776 B, 374 allocs).
Signed-off-by: sergioperezcheco <checo520@outlook.com>
goquery calls html.Parse, so callers cannot reach the parse options that
golang.org/x/net/html has since grown. The one that matters is
ParseOptionEnableScripting(false): with scripting assumed enabled the
contents of a noscript element are parsed as raw text and no selector
can reach inside it.
Add a constructor taking html.ParseOption values. Existing constructors
are untouched.
Refs #139
RemoveClass deleted each class with one strings.ReplaceAll pass over the
space-wrapped class list. Two adjacent occurrences share the space
between them, so " a a " holds only one non-overlapping " a " and the
second survived: RemoveClass("a") on class="a a" returned with
HasClass("a") still true. ToggleClass used the same expression and so
toggled such a class the wrong way.
Replace one occurrence at a time, re-scanning from the start, in a helper
shared by both call sites. Whitespace between the remaining classes is
untouched.
Adds goquery.Text(s, *TextOptions), a package-level counterpart to the
Selection.Text method that gives control over how the text of distinct
text nodes is joined (Separator), whether each node is trimmed of
surrounding whitespace (Trim), and which text nodes are included (Keep).
This covers the common need to extract clean, readable text from a
document - for example joining fragments with a space and dropping the
text of script/style elements - without hand-rolling a node walk.
Passing a nil TextOptions keeps the behaviour identical to Selection.Text.
Includes tests and a runnable example.
Attr was a 4-line wrapper around getAttributeValue, which itself was
a 6-line wrapper around getAttributePtr.
This commit inlines getAttributeValue into Attr and
have AttrOr delegate to Attr. Drops a layer of indirection and one
helper function; both methods get shorter and the empty-selection
check lives in one place.