Marpa::R2::HTML(3pm)
π Marpa::R2::HTML π
okay bestie, sit DOWN, because we're about to talk about Marpa::R2::HTML β from the Marpa::R2 family, manual section 3pm, and it's giving High-level HTML Parser energy and honestly? iconic.
β¨ NAME β¨
Marpa::R2::HTML β a High-level HTML Parser. that's it, that's the name, she's simple and she knows what she is π
πΈ SYNOPSIS πΈ
buckle up, we're doing examples. real ones. here's how you delete every single table:
use Marpa::R2::HTML qw(html);
my $with_table = 'Text<table><tr><td>I am a cell</table> More Text';
my $no_table = html( \$with_table,
{ table => sub { return q{} } });
now the opposite β deleting literally everything except tables:
my %handlers_to_keep_only_tables = (
table => sub { return Marpa::R2::HTML::original() },
':TOP' => sub { return \( join q{}, @{ Marpa::R2::HTML::values() } ) }
);
my $only_table = html( \$with_table, \%handlers_to_keep_only_tables );
so the way that works? it turns the original text of the HTML into values and then concatenates the values at the top of the parse. and it's not fragile either β the same logic still works even if a table is very defective:
my $with_bad_table = 'Text<tr>I am a cell</table> More Text';
my $only_bad_table =
html( \$with_bad_table, \%handlers_to_keep_only_tables );
deleting all the comments (no thoughts, head empty, comment-less):
my $with_comment = 'Text <!-- I am a comment --> I am not a comment';
my $no_comment = html( \$with_comment,
{ ':COMMENT' => sub { return q{} } }
);
by default text just passes through unchanged, which is soooo convenient β you only have to write semantic actions for the components you actually want changed. want a glow-up for a document title?
my $old_title = '<title>Old Title</title>A little html text';
my $new_title = html(
\$old_title,
{ 'title' => sub { return '<title>New Title</title>' }
}
);
delete every element rockin' a class attribute of "delete_me":
my $stuff_to_be_edited = '<p>A<p class="delete_me">B<p>C';
my $edited_stuff = html( \$stuff_to_be_edited,
{ '.delete_me' => sub { return q{} } });
and here's the part that lives in my head rent free: Marpa::R2::HTML recognizes elements even if they have missing start and/or end tags. it can literally supply the missing tags for you:
sub supply_missing_tags {
my $tagname = Marpa::R2::HTML::tagname();
return if Marpa::R2::HTML::is_empty_element($tagname);
return ( Marpa::R2::HTML::start_tag() // "<$tagname>\n" )
. Marpa::R2::HTML::contents() .
( Marpa::R2::HTML::end_tag() // "</$tagname>\n" );
}
my $html_with_just_a_title = '<title>I am a title and That is IT!';
my $valid_html_with_all_tags =
html( \$html_with_just_a_title, { q{*} => \&supply_missing_tags } );
she also fully understands the hierarchical structure of an HTML document, so finding the maximum nesting depth in elements is genuinely straightforward:
sub depth_below_me {
return List::Util::max( 0, @{ Marpa::R2::HTML::values() } );
}
my %handlers_to_calculate_maximum_element_depth = (
q{*} => sub { return 1 + depth_below_me() },
':TOP' => sub { return depth_below_me() },
);
my $maximum_depth_with_just_a_title = html( \$html_with_just_a_title,
\%handlers_to_calculate_maximum_element_depth );
and Marpa::R2::HTML tracks actual elements, however they're tagged. so the code above returns the same depth for $valid_html_with_all_tags as it does for $html_with_just_a_title. slay β¨
π¦ DESCRIPTION π¦
Marpa::R2::HTML does "high-level" parsing of HTML. it lets you specify handlers for elements, terminals, and all the other components hanging out in the hierarchical structure of an HTML document.
and bestie, she is an extremely liberal HTML parser. like, Marpa::R2::HTML does not reject any documents β no matter how poorly they fit the HTML standards. zero gatekeeping. no cap.
π THE Marpa::R2::HTML::html STATIC METHOD π
the interface to Marpa::R2::HTML is through the Marpa::R2::HTML::html static method. it's the only Marpa::R2::HTML method that is not part of the API for the semantic actions, so like, remember her name.
html takes one or more arguments:
- the first argument is required, and it must be a reference to a string to be parsed as HTML π«Ά
- the second and subsequent arguments (all optional) are hash references with handler descriptions.
(peep the synopsis above for several examples of calls using the html method, i know you wanna look.)
π CSS-style Handler Options π
handler descriptions in Marpa::R2::HTML are just key-value pairs in a hash. in each pair:
- the key is a CSS-style handler specifier
- the value is a closure, which is called the action for the handler
these specifiers are "CSS-style" β their syntax imitates some of the basic cases of CSS specifiers. and just so we're clear: no attempt is planned to implement the full CSS specifier syntax. so don't come for her about it π
here are the supported specifier syntaxes:
Tagname Specifiers
table => sub { return Marpa::R2::HTML::original() },
if a specifier contains no special characters, it's taken as the name of an element. (a "special" character = anything except an alphanumeric, a hyphen, or an underscore.) and consistent with HTML::Parser's default behavior, element names must be specified in lowercase.
Class Specifiers
a specifier that's a dot or period followed by a name matches any element whose class attribute is the same as the specified name. for example, the specifier ".delete_me" matches any element whose class attribute is "delete_me". so specific. so cute. π
Tagname-Class Pair Specifiers
a specifier containing a dot or period somewhere other than the first position (such as "span.label") is treated as a dotted tagname-class pair. its action gets called for any component whose tagname and class attribute both match the specifiers. both of them. it's giving overachiever.
The Tagname Wildcard Specifier
a specifier of just an asterisk ("*") matches all elements. but be careful bestie, note this: matching all elements is not the same as matching all components. the element wildcard specifier will not match any pseudoclasses. π
Pseudoclass Specifiers
':COMMENT' => sub { return q{} }
a specifier that begins with a colon (":") matches a pseudoclass. Marpa::R2::HTML defines pseudoclasses to deal with terminals and other non-element components of the HTML hierarchy. iykyk.
π Conflicting Specifiers π
at most one semantic action is called for each component. so when an element component matches several specifiers, the action is picked based on the most specific match, in this order:
- matches by tagname-class pair are the most specific.
- matches by class are the next most specific.
- matches by tagname are considered less specific than matches by class.
- the wildcard match is the least specific.
here's an example so you can see the vibe check in action:
my $html = <<'END_OF_HTML';
<span class="high">High Span</span>
<span class="low">Low Span</span>
<div class="high">High Div</div>
<div class="low">Low Div</div>
<div class="oddball">Oddball Div</div>
END_OF_HTML
our @RESULTS = ();
Marpa::R2::HTML::html(
\$html,
{ q{*} => sub {
push @RESULTS, 'wildcard handler: ' . Marpa::R2::HTML::contents();
},
'div' => sub {
push @RESULTS, '"div" handler: ' . Marpa::R2::HTML::contents();
},
'.high' => sub {
push @RESULTS, '".high" handler: ' . Marpa::R2::HTML::contents();
},
'div.high' => sub {
push @RESULTS,
'"div.high" handler: ' . Marpa::R2::HTML::contents();
},
'.oddball' => sub {
push @RESULTS,
'".oddball" handler: ' . Marpa::R2::HTML::contents();
},
'body' => sub {undef},
'head' => sub {undef},
'html' => sub {undef},
'p' => sub {undef},
}
);
and here's what $result would contain after that code runs (look at her go π):
".high" handler: High Span
wildcard handler: Low Span
"div.high" handler: High Div
"div" handler: Low Div
".oddball" handler: Oddball Div
π§ Details of the Specifier Syntax π§
for elements and class names, only alphanumerics, hyphens and underscores are supported. elements must be specified in lowercase, but they will match tagnames in the original document on a case-insensitive basis. flexible queen.
forcing element names to lowercase follows the default behavior of HTML::Parser, which coerces all tagnames to lowercase. this is consistent with the HTML standards. it is not consistent with the XML standards, and β heads up β an option to configure this behavior may be added in the future. so like, watch this space i guess πΈ
pseudoclass names special to Marpa::R2::HTML are case-sensitive, and must be all uppercase. lowercase is reserved for CSS pseudoclasses. and the CSS standard specifies that its pseudoclass names are case-indifferent. no CSS pseudoclasses are supported at this writing.
π PSEUDOCLASSES π
okay so here's the tea, bestie: Marpa::R2::HTML doesn't do its own low-level parsing. It outsources that to HTML::Parser. And those HTML::Parser "events"? They literally become the terminals for Marpa::R2::HTML. slay.
And it's not just terminals and elements, no ma'am. There are three more HTML components it recognizes:
- the SGML prolog β that's
":PROLOG" - the SGML trailer β that's
":TRAILER" - the HTML document as a whole β that's
":TOP"
π :CDATA π
The ":CDATA" pseudoclass is the one that says what to do for CDATA terminals. Its action gets called once for each non-whitespace raw "text" event that isn't reclassed as cruft. (Quick reminder, queen: "raw text" means text where any markup and entities should be left as is. don't touch, don't interpret. π
)
More precisely β and I'm being precise because this matters β a ":CDATA" terminal is created from any HTML::Parser "text" event that:
- has the
"is_cdata"flag on - contains a non-whitespace character as defined in the HTML 4.01 specification (
<http://www.w3.org/TR/html4/struct/text.html#h-9.1>) - is not reclassed as cruft
πΈ :COMMENT πΈ
The ":COMMENT" pseudoclass is the action spec for HTML comments. Its action gets called once for every "HTML::Parser" "comment" event that is not reclassed as cruft. one per comment, no notes. β¨
π :CRUFT π
The ":CRUFT" pseudoclass is where the messy stuff goes. Its action is called once for every "HTML::Parser" event that Marpa::R2::HTML reclasses as cruft.
So when does something become cruft? When it doesn't fit the structure of an HTML document. The example they give: a "</head>" end tag showing up in the HTML body. Like, bestie, what are you doing there. π«
But here's the thing β reclassing terminals as cruft is only the last resort. When it can, HTML::Parser forgives violations of the HTML standards and just accepts terminals as non-cruft. it's merciful fr fr.
Cruft gets treated pretty much like comments. It's preserved, untouched, in the original text view. π«Ά
π¦ :DECL π¦
The ":DECL" pseudoclass specifies the action for SGML declarations. Its action is called once for every "HTML::Parser" "declaration" event that is not reclassed as cruft.
β :PCDATA β
The ":PCDATA" pseudoclass specifies the action for PCDATA terminals. Its action is called once for each non-whitespace non-raw "text" event that is not reclassed as cruft.
More precisely, a ":PCDATA" terminal is created from any HTML::Parser "text" event that:
- has the
"is_cdata"flag off - contains a non-whitespace character as defined in the HTML 4.01 specification (
<http://www.w3.org/TR/html4/struct/text.html#h-9.1>) - is not reclassed as cruft
Markup and entities in ":PCDATA" text are expected to be interpreted eventually, but doing that during parsing can be straight-up counter-productive. Picture it: an application rewriting a document for display on the web. In that case it's usually gonna want to leave markup and entities alone so the client's browser can interpret them. π
Marpa::R2::HTML leaves interpretation of markup and entities entirely to the application. If an application wants to do the interpreting itself, it can do it in the actions, or handle it in post-processing. And CPAN has excellent tools for this β some of them are literally part of HTML::Parser. we love a supportive ecosystem. π
π :PI π
The ":PI" pseudoclass specifies the action for SGML processing instructions. Its action is called once for every HTML::Parser "process" event that is not reclassed as cruft.
π§ :PROLOG π§
The ":PROLOG" pseudoclass specifies the action for SGML prolog. This is the part of the HTML document that comes before the HTML root element. Components valid in the prolog include:
- SGML comments
- processing instructions
- whitespace
π :TOP π
okay this one's the main character. The action specified for the ":TOP" pseudoclass gets called once and only once in every parse, and it will be the last action called in every parse.
The ":TOP" component is the entire physical document β that's the SGML prolog, the root element, and the SGML trailer. Every other HTML component in a document is a descendant of the ":TOP" component. it's giving ancestor energy. π
The ":TOP" action is unique because there is always an action for it, even if you never specified one. The "html" method returns the value returned by the ":TOP" action. And the default ":TOP" action? It returns a reference to a string with the literal text value of all of its descendants. cute and functional. π
π :TRAILER π
The ":TRAILER" pseudoclass specifies the action for SGML trailer. This is the part of the HTML document that follows the HTML root element. Components valid in the trailer include:
- SGML comments
- processing instructions
- whitespace
Cruft can show up here too, though for Marpa::R2::HTML that's a last resort. π
β¨ :WHITESPACE β¨
A Marpa::R2::HTML ":WHITESPACE" terminal is created for every HTML::Parser "text" event that is entirely whitespace as defined in the HTML 4.01 specification ( <http://www.w3.org/TR/html4/struct/text.html#h-9.1> ) and that is not reclassed as cruft.
And ngl, this one's lowkey important: whitespace is acceptable in places where non-whitespace is not, and that difference can be very significant structurally. so don't sleep on the spaces, bestie. they're doing work. π
π¦ VIEWS π¦
Okay so I hope the synopsis already sold you on the fact that the action semantics of Marpa::R2::HTML are natural β¨. Like, genuinely natural. But bestie, that naturalness comes at the price of a little novelty β so this section is me explaining the whole idea behind the semantic action API π .
Depending on your taste, you can totally skip this section and go straight to the API. No judgment, live your truth π«Ά.
So here's the vibe: the components of an HTML document form a hierarchy, with the ":TOP" component on top, and the terminals on the bottom. The traditional syntax tree method requires semantic actions to know precisely what children every component will have. And that processing model? It's just not a good fit for HTML. Not even close π. So Marpa::R2::HTML gives the writer of semantic actions "views" of each component that fit way better in situations where the number and type of children is unknown or vaguely defined.
Marpa::R2::HTML's semantics focus more widely β on a component's descendants instead of just its direct children. (And the terms ancestor and descendant are used in the standard way: if a component X is above Y in the hierarchy, X is an ancestor of Y; and Y is a descendant of the X.) π
πΈ The Original View πΈ
The original view sees the text of a component as it was originally passed to the parser. The original view never changes. The original view is seen through the Marpa::R2::HTML::original API method. periodt π
π The Terminals View π
The terminals view sees the terminals corresponding to the original text of a component. The terminals view never changes either. The terminals view is usually seen as part of other views, iykyk.
At this writing, the API does not contain a "pure" terminals view method. For a terminals view of the whole HTML document, HTML::Parser does the job with significantly lower overhead π«‘. For views and sections of views with no values defined, the descendants view (described below β¬οΈ) is equivalent to the terminals view.
π The Values View π
When actions are called, they return a value. If that value is defined, it becomes visible to the values view of its ancestors. The values view of a component sees the visible values for its descendants.
The values view is an array, with the values ordered according to the lexical order of the components whose actions returned them. If no descendants have visible values, then the values view is a zero-length array. (Empty. Nothing. Zero. π)
The values view is hierarchical. When a component produces a visible value, it makes the values of its descendants disappear β like, poof, gone π«οΈ. So whenever the semantic action for a component X returns anything other than a Perl undef, it has two effects:
- That return value becomes the visible value associated with component X.
- All the values previously visible due to semantic actions for the descendants of component X disappear.
Values which disappear are gone forever. There is no mechanism to make them "reappear". None. Nada. Don't even ask π.
As a special case: if an action for a component returns a Perl undef, not only do the values of all its descendants disappear, the component for the action also will not appear in the values view. When its semantic action returns undef, a component permanently "drops out" of the values view, taking all descendants with it. The original view is seen through the Marpa::R2::HTML::values API method.
π The Literal View π
The literal view can be thought of as a mix between the original view and the values view. It sees a text string, like the original view. But unlike the original view, the literal view includes the visible values β¨.
Values appear in the literal view in stringized form. For sections of the original text without visible values, the literal view is the same as the original view. And in all of Marpa::R2::HTML's views, whether descendants are seen as text or values, they are seen in the original lexical order. The literal view is seen through the Marpa::R2::HTML::literal API method.
π§ The Descendants View π§
Just as the literal view can be thought of as a mix between the original view and the values view, the descendants view can be thought of a mix between the terminals view and the values view. π
The descendants view sees an array of elements with data for each of the component's descendants, in lexical order. Where a value is visible, the descendants view sees data for the component with the visible value. Where no value is visible, the descendants view sees data for the terminals. This means that when no values are visible, the descendants view is the same as the terminals view. (See? Told you it was the same. π)
The descendants view is implemented via the Marpa::R2::HTML::descendants method. It is the most fine-grained and detailed way to look at the descendants of a component. The descendants view can do anything that the other views can do, but the other views should be preferred when they fit the application β because other views are typically more intuitive and efficient π
.
β Views versus Syntax Trees β
Views are a generalization of the traditional method for processing semantics: syntax trees. The values view is the view that most closely resembles a syntax tree. But there are important differences, bestie.
In its purest form, the syntax tree model required the semantic actions to define exactly how many and what kind of immediate children each node had. Each node in a syntax tree worked with its immediate children. Children in a syntax tree appeared as values.
The values view, on the other hand, sees all its descendants, not just its immediate children β but only if they make themselves visible π. Because of this, the values view lends itself to being mixed with other views. The values view allows pieces of the tree to decide when they will come into sight and when they will fall out of view. Main character energy, honestly π«Ά.
π₯ Views and Efficiency π₯
In most applications, views are more efficient than syntax trees. In terms of Marpa::R2::HTML views, traditional syntax tree processing corresponds most closely to the values view when every component in the parse has a visible value. And for Marpa::R2::HTML, that is close to the worst case. π
Marpa::R2::HTML optimizes for unvalued components. Unvalued components are represented as terminal spans. Adjacent descendant spans are automatically merged. This means the size and time required do not increase as processing rises up the component hierarchy. Slay π.
Terminals views are calculated on a just-in-time basis when they are requested through the action API. The terminals view is produced quickly from the merged terminal span.
Original views are also calculated on a just-in-time basis as requested. Each terminal tracks the text it represents as a character span. The original text can be quickly reconstructed as the text in the source document from the first character location of its component's first terminal to the last character location of the component's last terminal.
And when a handler does not need to return a value, the most efficient thing to do is to return undef. This reverts that component and all its descendants to the efficient unvalued representation. Chef's kiss π¨βπ³π
β¨ THE SEMANTIC ACTION API β¨
okay so the semantic action API in Marpa::R2::HTML is mostly a bunch of context-aware static methods, and honestly? that's the whole tea. Your semantic action callbacks get zero arguments passed to them. nothing. nada. instead, your callbacks go grab whatever they need by calling these static methods like the independent main characters they are π
πΈ API Static Methods πΈ
Marpa::R2::HTML::attributesβ hands you a hash ref to the attributes of the start tag. and bestie, this is literally the exact same hash ref you'd get for the"attr"arg specification ofHTML::Parser. If there were no attributes, or there was no start tag for this element, or the current component isn't an element at all? you get an empty hash back. rip πMarpa::R2::HTML::contentsβ for an element, gives you the literal view of the contents. and "contents" means the entire text of the element minus its start tag and its end tag. for a non-element component?undef. sorry π«ΆMarpa::R2::HTML::descendantsβ this one implements the descendants view. it takes exactly one argument, the"dataspec". the dataspec is a string telling it what data to return for each descendant. you get back a reference to an array with one element per descendant, in lexical order, and each element of that array is a reference to an array whose elements are the per-descendant data you asked for in the string. the dataspec syntax is similar to the"argspec"strings ofHTML::Parserβ deets below, keep scrolling β¬οΈMarpa::R2::HTML::end_tagβ for an element with an explicit end tag, returns the original text of that end tag. for non-element components,undef. for elements with no end tag,undef. so many undefs, so little time πMarpa::R2::HTML::is_empty_elementβ for an element, returns a Perl true value if the element is empty, and a defined Perl false value otherwise. for non-element components, returnsundef. nuance! β¨Marpa::R2::HTML::literalβ implements the literal view. returns a string with the literal view of the component β aka its text as modified by any of the visible values of its descendants.Marpa::R2::HTML::literal_refβ same energy as above but gives you a reference to a string containing the literal view. super handy when the strings are very, very long. you're welcome πMarpa::R2::HTML::offsetβ returns the start offset of the component, a zero-based location in the source document. some components are zero-length, like they contain none of the tokens in the physical input at all. for those, this method returns"undef". ghost components fr π»Marpa::R2::HTML::originalβ implements the original view. returns a string containing the original view of the component β its text completely unchanged from the source document.Marpa::R2::HTML::start_tagβ for an element with an explicit start tag, returns the original text of the start tag. for non-element components,undef. for elements with no explicit start tag,undef. yes it's symmetrical withend_tag, we love consistency πMarpa::R2::HTML::tagnameβ for an element component, returns its tagname. and here's the thing: there's a tagname even if there are no explicit tags. the tagname gets determined based on structure. for non-element components,undef.Marpa::R2::HTML::titleβ returns the value of the title attribute. for a non-element component,undef. if there was no explicit start tag,undef. if there was no title attribute,undef. you already knew that last one but ok slay πMarpa::R2::HTML::token_typeβ for a token, returns the token type. the token types are the event types fromHTML::Parser:"T"for text,"S"for a start tag,"E"for an end tag,"PI"for a processing instruction,"D"for an SGML declaration, and"C"for a comment. if the component is an element or some other non-token, returnsundef.Marpa::R2::HTML::valuesβ implements the values view. returns a reference to an array of the descendant values visible from this component, in lexical order. no elements of this array will ever be undefined, iconic behavior. the array is zero length if no descendant has a visible value. empty but valid π
π¦ Dataspecs π¦
Marpa::R2::HTML::descendants('token_type,literal,element')
the data specification string, or dataspec, is a comma separated list of descendant data specifiers. Marpa::R2::HTML::descendants takes a dataspec as its one argument, and returns a reference to an array of references to arrays of per-descendant data. the contents of those per-descendant data arrays, and their order, is exactly what the dataspec said. here are the valid descendant data specifiers, bestie:
"element"β for an element descendant, returns the tagname. and yes, a valid tagname gets returned even if there were no explicit tags. for non-element descendants,undef."literal"β returns a string containing the literal view of the descendant."original"β returns a string containing the original view of the descendant."token_type"β if the descendant is a terminal, returns the token type. token types are exactly as described for theMarpa::R2::HTML::token_typeAPI method. for components with visible values, returnsundef."value"β for element descendants with a value, returns that value. in all other cases, returnsundef. that's it, that's the whole specifier π
π The Instance Hash π
every Marpa::R2::HTML instance hands you a per-instance variable as a little scratchpad for your app: $Marpa::R2::HTML::INSTANCE. every single call to Marpa::R2::HTML::html creates a fresh $Marpa::R2::HTML::INSTANCE variable reserved for that application using the "local" keyword. Marpa::R2::HTML::html initializes it to an empty hash, and after that? it does not touch it. hands off. when programming via side effects is more natural than passing data up the parse tree β and lowkey it often is β $Marpa::R2::HTML::INSTANCE is right there for storing your data π«Ά
ordinarily, $Marpa::R2::HTML::INSTANCE gets destroyed along with the rest of the parse instance when "Marpa::R2::HTML::html" returns. but it can be genuinely useful for the ":TOP" semantic action to return $Marpa::R2::HTML::INSTANCE as the value of the parse. main character exit π
π Undefined Actions versus Actions Which Return "undef" π
this deserves its own dramatic spotlight moment, so: the effect of not defining a semantic action for a component is different from the effect of defining a semantic action which returns a Perl "undef". the difference lives entirely in what happens to any visible values of that component's descendants. read carefully, bestie:
- Where no action is defined for a component, it leaves all that component's views exactly as they were before. meaning: all values which were visible stay visible, and no new values become visible.
- When an action is defined for a component but that action returns
undef, no new values become visible, and all descendant values which were visible disappear. poof. gone. π
not me emphasizing this enough times β this is the kind of thing that silently eats your output if you're not paying attention
π Root Element versus :TOP Pseudoclass π
you really need to understand the very special function of the ":TOP" component, and please don't confuse it with the HTML root element. the most important distinctions are:
- The semantic action for the
":TOP"pseudoclass is always the last action to be called in a parse. - The
":TOP"component is always the entire HTML document. this can be true of the root element, but it is not true in all cases. - The value that the action for the
":TOP"component returns becomes the value that theMarpa::R2::HTML::htmlmethod returns.
the root element is the HTML element whose tagname is "html", though its start and end tags are optional and can be omitted even in strictly valid HTML. tags or no tags, every HTML document has a root element. (meanwhile the ":TOP" component is not an element at all, so it has no tagname and never has tags. periodt β¨)
the root element is always a descendant of the ":TOP" component. the SGML prolog and SGML trailer are always descendants of the ":TOP" component too. the SGML prolog and SGML trailer are never descendants of the root element. family tree sorted πΈ
if an action for the root element is specified, it will also be called once and only once in every parse. you can specify an action for the root element the same way as actions for other elements β just use its tagname of "html". an element wildcard action also becomes the action for the root element, if no more specific handler declaration takes precedence.
a ":TOP" action gets called once and only once in every parse. and the ":TOP" action is unique in that it has a default action. no other component has a default action. she's special, she knows it π
π Tags versus Structure π
where tags conflict with structure, HTML::Parser follows structure. and "following structure" means, for example, that if semantic actions for the "html", "head", and "body" elements exist, they'll each be called once and only once during every parse.
consider this short and very defective HTML document:
<title>Short</title><p>Text</head><head>
HTML::Parser starts the HTML document's body when it encounters the <p> start tag. which means that, even if they were in the right order, those two head tags simply cannot be fit into any reasonable parse structure. the math isn't mathing π
if an action is specified for the "head" element, it gets called for the actual header, and the original view of the "head" element component will be the text "<title>Short</title>". the action for the "head" element will not be called again. the two stray tags, "</head>" and "<head>", get treated as descendants of the "body" element, and reclassed as "cruft" terminals. cruft!! iconic name honestly π
π§ Explicit and Implicit Elements π§
if a semantic action is specified for a tagname, it gets called whenever an element is found with that tagname β even if there are no explicit tags for that element. the HTML standards allow both start and end tags to be missing for "html", "head", "body" and "tbody" elements. Marpa::R2::HTML is more liberal than that, and will recognize virtual tags for "table", "tr", and "td" elements as required to repair a defective table. she's a fixer π οΈ
and Marpa::R2::HTML is even more liberal about recognizing virtual end tags than it is about start tags. virtual start tags are recognized only for the specific elements listed above. but for any non-empty HTML element, there is some circumstance under which Marpa::R2::HTML will recognize a virtual end tag. at end of file, as one example, Marpa::R2::HTML will do its best to produce a balanced HTML structure by creating a virtual end tag for every element in the stack of currently active elements. wrapping up loose ends like the queen she is π
π EXPORTS π
Marpa::R2::HTML exports nothing by default. optionally, though? Marpa::R2::HTML::html may be exported. your choice, bestie β¨
π Copyright and License π
okay bestie, legal bit incoming, but it's actually important so don't scroll past π this is the Copyright 2022 Jeffrey Kegler situation, and here's the whole vibe:
This file is part of Marpa::R2. Marpa::R2 is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. β¨
Marpa::R2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. π no cap, that's the standard "we gave you this for free, please don't come for us" energy.
You should have received a copy of the GNU Lesser General Public License along with Marpa::R2. If not, see http://www.gnu.org/licenses/. π«Ά
lowkey the whole license is like the group chat rules: share the love, credit the source, and don't blame anyone when stuff breaks. iykyk π
and that's a wrap on this whole page, bestie!! π this was Marpa::R2::HTML(3pm) from perl v5.42.3, dated 2026-08-20, part of the User Contributed Perl Documentation collection. ty for reading all the way to the bottom, you're literally the main character ππ go parse something iconic β¨π¦
π¦ where this page comes from
- ubuntu stonking: libmarpa-r2-perl 12.000000-1build1 Β· LGPL-3+~Marpa
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms