CSS::D...Parser(3pm)
π CSS::DOM::PropertyParser(3pm) π
User Contributed Perl Documentation. Bestie, this is the manual page for CSS::DOM::PropertyParser β the header squishes it down to CSS::D...Parser(3pm) because terminals are dramatic like that. Anyway. Grab your boba π§, we're parsing CSS property values today and it's lowkey a whole vibe.
πΈ NAME πΈ
CSS::DOM::PropertyParser β Parser for CSS property values. That's the whole thing. Iconic. β¨
β VERSION β
Version 0.17. Periodt.
π¦ SYNOPSIS π¦
use CSS::DOM::PropertyParser;
$spec = new CSS::DOM::PropertyParser; # empty
# OR
$spec = $CSS::DOM::PropertyParser::Default->clone;
$spec->add_property(
overflow => {
format => 'visible|hidden|scroll|auto',
default => 'visible',
inherit => 0,
}
);
$hashref = $spec->get_property('overflow');
$hashref = $spec->delete_property('overflow');
@names = $spec->property_names;
π DESCRIPTION π
Okay so, objects of this class are literally just lists of supported properties for CSS::DOM style sheets. But they also describe the syntax and parsing of those properties' values, so they're doing the most. π
Some CSS properties simply have their own values (like overflow, a queen who needs no help). Others are abbreviated forms of several other properties (like font, the friend group). In this documentation those are called 'simple' and 'shorthand' properties. Remember that, it comes up again. π
π CONSTRUCTOR π
$spec = new CSS::DOM::PropertyParser returns an object that does not recognise any properties β it's a blank canvas, an empty notebook, a fresh face β and you can add your own properties to it. π«Ά
There are two parser objects that come with this module, no cap:
$CSS::DOM::PropertyParser::CSS21β contains all of CSS 2.1.$CSS::DOM::PropertyParser::Defaultβ currently identical to the former, but parts of CSS 3 will eventually be added to it.
If one of the default specs will do, you don't need a constructor at all. Just pass it to the CSS::DOM constructor. If you want to modify it, clone it first, using the clone method (as shown in the "SYNOPSIS"). It is often convenient to clone the $Default spec and delete those properties that are not supported. Slay. π
β¨ METHODS β¨
cloneβ Returns a deep clone of the object. (It's deep so that you can modify the hashes/arrays inside it without modifying the original.) Very demure, very mindful. πadd_property ( $name, \%spec )β Adds the specification for the named property. See "HOW INDIVIDUAL PROPERTIES ARE SPECIFIED", below.get_property ( $name )β Returns the hashref passed to the previous method.delete_property ( $name )β Deletes the property and returns the hash ref.property_namesβ Returns a list of the names of supported properties.subproperty_names ( $name )β Returns a list of the names of$name's sub-properties if it is a shorthand property.matchβ Currently for internal use only. See the source code for documentation. Use at your own risk. π
π HOW INDIVIDUAL PROPERTIES ARE SPECIFIED π
Before you read this the first time, look at the "Example" below, and then come back and use this for reference. (Not me telling you to read the example first, but also yes, read the example first. π)
The specification for an individual property is a hash ref. There are several keys that each hash ref can have:
formatβ This is set to a string that describes the format of the property. The syntax used is based on the CSS 2.1 spec, but is not exactly the same. Unlike regular expressions, these formats are applied to properties on a token-by-token basis, not one character at a time. (This means that100|200cannot be written as[1|2]00, as that would mean1 00 | 2 00.)
Whitespace is ignored in the format and in the CSS property except as a token separator.
There are several metachars (in order of precedence):
[...]β grouping (like(?:...))(...)β capturing group (just like a regexp)?β optional*β zero or more+β one or more||β alternates that can come in any order and are optional, but at least one must be specified (the order will be retained if possible)|β alternates, exactly one of which is required
In addition, the following datatypes can be specified in angle brackets:
<angle>β A number with a 'deg', 'rad' or 'grad' suffix<attr>βattr(...)<colour>β (You can omit the 'u' if you want to.) One of CSS's predefined colour or system colour names, or a#followed by 3 or 6 hex digits, or the'rgb(...)'format (rgba is supported, too)<counter>βcounter(...)<frequency>β A unit of Hz or kHz<identifier>β An identifier token<integer>β An integer (really?!)<length>β Number followed by a length unit (em, ex, px, in, cm, mm, pt, pc)<number>β A number token<percentage>β Number followed by %<shape>βrect(...)<string>β A string token<str/words>β A sequence of identifiers or a single string (e.g., a font name)<time>β A unit of seconds or milliseconds<url>β A URL token
The format for a shorthand property can contain the name of a sub-property in single ASCII quotes.
All other characters are understood verbatim.
It is not necessary to include the word 'inherit' in the format, since every property supports that.
<counter> makes use of the specification for the list-style-type property. So if you modify the latter, it will affect <counter> as well.
defaultβ The default value. This only applies to simple properties.inheritβ Whether the property is inherited.special_valuesβ A hash ref of values that are replaced with other values (e.g.,caption => '13px sans-serif'). The keys are lowercase identifier names.
This feature only applies to single identifiers. In fact, it exists solely for the font property's use.
listβ Set to true if the property is a list of values. The capturing parentheses in the format determine the individual values of the list.
This applies to simple properties only.
propertiesβ For a shorthand property, list the sub-properties here. The keys are the property names. The values are array refs. The elements within the arrays are numbers indicating which captures in the format are to be used for the sub-property's value. They are tried one after the other. Whichever is the first that matches (null matches not counting) is used.
Sub-properties that are referenced in the "format" need not be listed here.
serialiseβ For shorthand properties only. Set this to a subroutine that serialises the property. It is called with a hashref of sub-properties as its sole argument. The values of the hash are blank for properties that are set to their initial values. This sub is only called when all sub-properties are set.
π Example π
our $CSS21 = new CSS::DOM::PropertyParser;
my %properties = (
azimuth => {
format => '<angle> |
[ left-side | far-left | left | center-left |
center | center-right | right | far-right |
right-inside ] || behind
| leftwards | rightwards',
default => '0',
inherit => 1,
},
'background-attachment' => {
format => 'scroll | fixed',
default => 'scroll',
inherit => 0,
},
'background-color' => {
format => '<colour>',
default => 'transparent',
inherit => 0,
},
'background-image' => {
format => '<url> | none',
default => 'none',
inherit => 0,
},
'background-position' => {
format => '[<percentage>|<length>|left|right]
[<percentage>|<length>|top|center|bottom]? |
[top|bottom] [left|center|right]? |
center [<percentage>|<length>|left|right|top|bottom|
center]?',
default => '0% 0%',
inherit => 0,
},
'background-repeat' => {
format => 'repeat | repeat-x | repeat-y | no-repeat',
default => 'repeat',
inherit => 0,
},
background => {
format => "'background-color' || 'background-image' ||
'background-repeat' || 'background-attachment' ||
'background-position'",
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ background-color background-image background-repeat
background-attachment background-position /) {
length $p->{$_} and $ret .= "$p->{$_} ";
}
chop $ret;
length $ret ? $ret : 'none'
},
},
'border-collapse' => {
format => 'collapse | separate',
inherit => 1,
default => 'separate',
},
'border-color' => {
format => '(<colour>)[(<colour>)[(<colour>)(<colour>)?]?]?',
properties => {
'border-top-color' => [1],
'border-right-color' => [2,1],
'border-bottom-color' => [3,1],
'border-left-color' => [4,2,1],
},
serialise => sub {
my $p = shift;
my @vals = map $p->{"border-$_-color"},
qw/top right bottom left/;
$vals[3] eq $vals[1] and pop @vals,
$vals[2] eq $vals[0] and pop @vals,
$vals[1] eq $vals[0] and pop @vals;
return join " ", @vals;
},
},
'border-spacing' => {
format => '<length> <length>?',
default => '0',
inherit => 1,
},
'border-style' => {
format => "(none|hidden|dotted|dashed|solid|double|groove|ridge|
inset|outset)
[ (none|hidden|dotted|dashed|solid|double|groove|
ridge|inset|outset)
[ (none|hidden|dotted|dashed|solid|double|groove|
ridge|inset|outset)
(none|hidden|dotted|dashed|solid|double|groove|
ridge|inset|outset)?
]?
]?",
properties => {
'border-top-style' => [1],
'border-right-style' => [2,1],
'border-bottom-style' => [3,1],
'border-left-style' => [4,2,1],
},
serialise => sub {
my $p = shift;
my @vals = map $p->{"border-$_-style"},
qw/top right bottom left/;
$vals[3] eq $vals[1] and pop @vals,
$vals[2] eq $vals[0] and pop @vals,
$vals[1] eq $vals[0] and pop @vals;
return join " ", map $_||'none', @vals;
},
},
(to be continuedβ¦ part 2 is on its way, bestie π)
okay so we're in the shorthand era now bestie, and this is where it gets genuinely iconic. Every property below is just a hash ref with the same lil keys β format, default, inherit, sometimes properties, sometimes serialise, sometimes list or special_values β and the shorthands are the ones doing the most. π
First up: the four side shorthands. Each one is a || combo of that side's width, style, and colour, and each one carries a serialise sub that walks width style color, appends whichever ones are non-empty (with a space), and then chops the trailing space off the end:
'border-top' => {
format => "'border-top-width' || 'border-top-style' ||
'border-top-color'",
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ width style color /) {
length $p->{"border-top-$_"}
and $ret .= $p->{"border-top-$_"}." ";
}
chop $ret;
$ret
},
},
'border-right' => {
format => "'border-right-width' || 'border-right-style' ||
'border-right-color'",
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ width style color /) {
length $p->{"border-right-$_"}
and $ret .= $p->{"border-right-$_"}." ";
}
chop $ret;
$ret
},
},
'border-bottom' => {
format => "'border-bottom-width' || 'border-bottom-style' ||
'border-bottom-color'",
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ width style color /) {
length $p->{"border-bottom-$_"}
and $ret .= $p->{"border-bottom-$_"}." ";
}
chop $ret;
$ret
},
},
'border-left' => {
format => "'border-left-width' || 'border-left-style' ||
'border-left-color'",
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ width style color /) {
length $p->{"border-left-$_"}
and $ret .= $p->{"border-left-$_"}." ";
}
chop $ret;
$ret
},
},
see how they're all literally the same sub with a different side name? copy-paste queen behaviour, and honestly? valid. β¨
next, the per-side colours. Format is <colour>, the default is the empty string "" (not 'none', not 'transparent' β literally nothing), and inherit is 0 for all four:
'border-top-color' => {
format => '<colour>',
default => "",
inherit => 0,
},
'border-right-color' => {
format => '<colour>',
default => "",
inherit => 0,
},
'border-bottom-color' => {
format => '<colour>',
default => "",
inherit => 0,
},
'border-left-color' => {
format => '<colour>',
default => "",
inherit => 0,
},
then the styles β <rainbow>none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset</rainbow> β defaulting to 'none', still inherit => 0:
'border-top-style' => {
format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
inset|outset',
default => 'none',
inherit => 0,
},
'border-right-style' => {
format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
inset|outset',
default => 'none',
inherit => 0,
},
'border-bottom-style' => {
format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
inset|outset',
default => 'none',
inherit => 0,
},
'border-left-style' => {
format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
inset|outset',
default => 'none',
inherit => 0,
},
and the widths β <length>|thin|thick|medium β defaulting to 'medium', inherit => 0:
'border-top-width' => {
format => '<length>|thin|thick|medium',
default => 'medium',
inherit => 0,
},
'border-right-width' => {
format => '<length>|thin|thick|medium',
default => 'medium',
inherit => 0,
},
'border-bottom-width' => {
format => '<length>|thin|thick|medium',
default => 'medium',
inherit => 0,
},
'border-left-width' => {
format => '<length>|thin|thick|medium',
default => 'medium',
inherit => 0,
},
okay now the good part. π border-width is the 1-to-4 value shorthand, and it's the first one we've seen that uses a properties hash to say "value slot 1 goes here, slot 2 goes here, but fall back to slot 1 if there's no slot 2" β you can literally read the CSS cascade rules off the array refs:
'border-width' => {
format => "(<length>|thin|thick|medium)
[ (<length>|thin|thick|medium)
[ (<length>|thin|thick|medium)
(<length>|thin|thick|medium)?
]?
]?",
properties => {
'border-top-width' => [1],
'border-right-width' => [2,1],
'border-bottom-width' => [3,1],
'border-left-width' => [4,2,1],
},
serialise => sub {
my $p = shift;
my @vals = map $p->{"border-$_-width"},
qw/top right bottom left/;
$vals[3] eq $vals[1] and pop @vals,
$vals[2] eq $vals[0] and pop @vals,
$vals[1] eq $vals[0] and pop @vals;
return join " ", map length $_ ? $_ : 'medium', @vals;
},
},
and the serialise is lowkey a little genius move: it grabs all four sides in top right bottom left order, then pops off the end whenever a value is redundant with the one it'd inherit from (left vs right, bottom vs top, right vs top), then joins what's left β swapping any empty string for 'medium' on the way out. β¨ and yes, note those sneaky trailing commas after pop @vals β the comma operator said "and also", that's just how it's written here, don't @ me.
then border, the big one. It's width || style || colour, and it maps all twelve longhands off three slots β slots 1, 2 and 3 fan out to every side:
border => {
format => "(<length>|thin|thick|medium) ||
(none|hidden|dotted|dashed|solid|double|groove|ridge|
inset|outset) || (<colour>)",
properties => {
'border-top-width' => [1],
'border-right-width' => [1],
'border-bottom-width' => [1],
'border-left-width' => [1],
'border-top-style' => [2],
'border-right-style' => [2],
'border-bottom-style' => [2],
'border-left-style' => [2],
'border-top-color' => [3],
'border-right-color' => [3],
'border-bottom-color' => [3],
'border-left-color' => [3],
},
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ width style color /) {
my $temp = $p->{"border-top-$_"};
for my $side(qw/ right bottom left /) {
$temp eq $p->{"border-$side-$_"} or return "";
}
length $temp and $ret .= "$temp ";
}
chop $ret;
$ret
},
},
heads up tho π β that serialise refuses to produce a shorthand at all if the four sides disagree: the second any side's width/style/colour doesn't match top's, it returns the empty string. no shorthand for you, bestie, it's giving "you have to write it longhand".
and then a whole lil run of single-value properties, each just a format + default + inherit:
bottom => {
format => '<length>|<percentage>|auto',
default => 'auto',
inherit => 0,
},
'caption-side' => {
format => 'top|bottom',
default => 'top',
inherit => 1,
},
clear => {
format => 'none|left|right|both',
default => 'none',
inherit => 0,
},
clip => {
format => '<shape>|auto',
default => 'auto',
inherit => 0,
},
color => {
format => '<colour>',
default => 'rgba(0,0,0,1)',
inherit => 1,
},
content => {
format => '( normal|none|open-quote|close-quote|no-open-quote|
no-close-quote|<string>|<url>|<counter>|<attr> )+',
default => 'normal',
inherit => 0,
list => 1,
},
notable moments: color defaults to 'rgba(0,0,0,1)' (yes, with the alpha, very specific of it π€), content is a +-quantified list of things and carries list => 1, and caption-side/color are the ones here with inherit => 1 while the rest are 0. keep up, there's a quiz later. π¦
counters next β both identical shapes, [(<identifier>) (<integer>)? ]+ | none, default 'none', inherit => 0, and both flagged list => 1:
'counter-increment' => {
format => '[(<identifier>) (<integer>)? ]+ | none',
default => 'none',
inherit => 0,
list => 1,
},
'counter-reset' => {
format => '[(<identifier>) (<integer>)? ]+ | none',
default => 'none',
inherit => 0,
list => 1,
},
auditory cue vibes now π₯ β cue-before and cue-after are each just <url>|none with a 'none' default, and then cue is the shorthand that folds them together:
'cue-after' => {
format => '<url>|none',
default => 'none',
inherit => 0,
},
'cue-before' => {
format => '<url>|none',
default => 'none',
inherit => 0,
},
cue =>{
format => '(<url>|none) (<url>|none)?',
properties => {
'cue-before' => [1],
'cue-after' => [2,1],
},
serialise => sub {
my $p = shift;
my @vals = @$p{"cue-before", "cue-after"};
$vals[1] eq $vals[0] and pop @vals;
return join " ", map length $_ ? $_ : 'none', @vals;
},
},
spot the hash slice @$p{"cue-before", "cue-after"} β grabbing both at once, efficient queen π
β then popping the second value if it matches the first, then joining with 'none' as the empty fallback. and yes, cue =>{ has no space before the brace, we don't gatekeep formatting here. π
more single-value properties! cursor is a comma-list of urls plus a keyword set and it's list => 1, inherit => 1, default 'auto':
cursor => {
format => '[(<url>) ,]*
(auto|crosshair|default|pointer|move|e-resize|
ne-resize|nw-resize|n-resize|se-resize|sw-resize|
s-resize|w-resize|text|wait|help|progress)',
default => 'auto',
inherit => 1,
list => 1,
},
direction => {
format => 'ltr|rtl',
default => 'ltr',
inherit => 1,
},
display => {
format => 'inline|block|list-item|run-in|inline-block|table|
inline-table|table-row-group|table-header-group|
table-footer-group|table-row|table-column-group|
table-column|table-cell|table-caption|none',
default => 'inline',
inherit => 0,
},
display is long bestie, count them β every single one of those values is in there, run-in included, and it defaults to 'inline' with inherit => 0. π
and a lil trio:
elevation => {
format => '<angle>|below|level|above|higher|lower',
default => '0',
inherit => 1,
},
'empty-cells' => {
format => 'show|hide',
default => 'show',
inherit => 1,
},
float => {
format => 'left|right|none',
default => 'none',
inherit => 0,
},
note elevation's default is the string '0' β quoted, a string, not a number β and float here is the CSS float, not your float, sorry. π
font block incoming and this is where the # aka typeface comment lives, the format is a comma-separated list with <str/words> as the escape hatch, default is 'Times, serif', and it's got inherit => 1 and list => 1:
'font-family' => { # aka typeface
format => '(serif|sans-serif|cursive|fantasy|monospace|
<str/words>)
[,(serif|sans-serif|cursive|fantasy|monospace|
<str/words>)]*',
default => 'Times, serif',
inherit => 1,
list => 1,
},
'font-size' => {
format => 'xx-small|x-small|small|medium|large|x-large|xx-large|
larger|smaller|<length>|<percentage>',
default => 'medium',
inherit => 1,
},
'font-style' => {
format => 'normal|italic|oblique',
default => 'normal',
inherit => 1,
},
'font-variant' => {
format => 'normal | small-caps',
default => 'normal',
inherit => 1,
},
'font-weight' => {
format => 'normal|bold|bolder|lighter|
100|200|300|400|500|600|700|800|900',
default => 'normal',
inherit => 1,
},
all five of those longhands are inherit => 1, defaults 'medium' / 'normal' as you'd expect, and font-weight runs the full 100β900 hundred-scale like the icon it is. β¨
then the font shorthand itself, and the way that it has a whole special_values hash handing out actual system-ish font stacks for the magic keywords β caption, icon, menu, 'message-box', 'small-caption', 'status-bar' β each one mapping to a literal size + family string:
font => {
format => "[ 'font-style' || 'font-variant' || 'font-weight' ]?
'font-size' [ / 'line-height' ]? 'font-family'",
special_values => {
caption => '13px Lucida Grande, sans-serif',
icon => '13px Lucida Grande, sans-serif',
menu => '13px Lucida Grande, sans-serif',
'message-box' => '13px Lucida Grande, sans-serif',
'small-caption' => '11px Lucida Grande, sans-serif',
'status-bar' => '10px Lucida Grande, sans-serif',
},
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ style variant weight /) {
length $p->{"font-$_"}
and $ret .= $p->{"font-$_"}." ";
}
$ret .= length $p->{'font-size'}
? $p->{'font-size'}
: 'medium';
$ret .= "/$p->{'line-height'}" if length $p->{'line-height'};
$ret .= " " . ($p->{'font-family'} || "Times, serif");
$ret
},
},
read that serialise carefully bestie π β style, variant and weight get appended only if non-empty, then font-size (or 'medium' if there's nothing), then line-height only if it exists, glued on with a slash, then the family β or "Times, serif" if there isn't one. it always emits a size and a family. no cap. π«Ά
and finally, closing out this stretch, height:
height => {
format => '<length>|<percentage>|auto',
default => 'auto',
inherit => 0,
},
<length>, <percentage>, or auto, defaulting to 'auto', inherit => 0. classic. π
okay so we're STILL in the giant property table and it's giving "encyclopedia but make it fashion" π
every entry is a little hashref with its own format, default, inherit, and sometimes a properties map or a serialise sub. buckle up bestie, there are like 40 more of these and we're doing every single one, no skips, no cap. π«Ά
left => {
format => '<length>|<percentage>|auto',
default => 'auto',
inherit => 0,
},
'letter-spacing' => { # aka tracking
format => 'normal|<length>',
default => 'normal',
inherit => 1,
},
'line-height' => { # aka leading
format => 'normal|<number>|<length>|<percentage>',
default => "normal",
inherit => 1,
},
'list-style-image' => {
format => '<url>|none',
default => 'none',
inherit => 1,
},
'list-style-position' => {
format => 'inside|outside',
default => 'outside',
inherit => 1,
},
'list-style-type' => {
format => 'disc|circle|square|decimal|decimal-leading-zero|
lower-roman|upper-roman|lower-greek|lower-latin|
upper-latin|armenian|georgian|lower-alpha|
upper-alpha',
default => 'disc',
inherit => 1,
},
'list-style' => {
format => "'list-style-type'||'list-style-position'||
'list-style-image'",
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ type position image /) {
$p->{"list-style-$_"}
and $ret .= $p->{"list-style-$_"}." ";
}
chop $ret;
$ret || 'disc'
},
},
see how left is a loner with inherit => 0 while letter-spacing and line-height ride or die with inherit => 1? the way that letter-spacing is literally tagged # aka tracking and line-height is # aka leading β the author knew. π
and then the list-style girlies: list-style-image, list-style-position, list-style-type each standing on their own, then the shorthand list-style rolls up all three with a serialise sub that loops qw/ type position image /, glues them with " ", chops the trailing space, and falls back to 'disc' if the string came out empty. iconic behaviour honestly. β¨
π the margin & padding posse π
next up it's the box-model besties. margin-right, margin-left, margin-top, margin-bottom all rock format => '<length>|<percentage>|auto' with default => '0' and inherit => 0 β except wait, left above also had default => 'auto'? nope, different property, don't get it twisted. π
'margin-right' => {
format => '<length>|<percentage>|auto',
default => '0',
inherit => 0,
},
'margin-left' => {
format => '<length>|<percentage>|auto',
default => '0',
inherit => 0,
},
'margin-top' => {
format => '<length>|<percentage>|auto',
default => '0',
inherit => 0,
},
'margin-bottom' => {
format => '<length>|<percentage>|auto',
default => '0',
inherit => 0,
},
margin => {
format => "(<length>|<percentage>|auto)
[ (<length>|<percentage>|auto)
[ (<length>|<percentage>|auto)
(<length>|<percentage>|auto)?
]?
]?",
properties => {
'margin-top' => [1],
'margin-right' => [2,1],
'margin-bottom' => [3,1],
'margin-left' => [4,2,1],
},
serialise => sub {
my $p = shift;
my @vals = map $p->{"margin-$_"},
qw/top right bottom left/;
$vals[3] eq $vals[1] and pop @vals,
$vals[2] eq $vals[0] and pop @vals,
$vals[1] eq $vals[0] and pop @vals;
return join " ", map $_ || 0, @vals;
},
},
okay the margin shorthand is main character energy. the format is a nested optional party: 1 to 4 values, each one can be a length, a percentage, or auto, and the brackets just keep nesting with ? at every level so you can pass one, two, three, or four. the properties map says who eats which slot:
'margin-top'gets[1]'margin-right'gets[2,1]'margin-bottom'gets[3,1]'margin-left'gets[4,2,1]
(that's the classic "fall back to the previous one" trick β iykyk.) and the serialise sub pulls qw/top right bottom left/, then pops off duplicates backwards ($vals[3] eq $vals[1], then $vals[2] eq $vals[0], then $vals[1] eq $vals[0]) so it only prints the shortest form, and join " ", map $_ || 0, @vals finishes the job. π§
'max-height' => {
format => '<length>|<percentage>|none',
default => 'none',
inherit => 0,
},
'max-width' => {
format => '<length>|<percentage>|none',
default => 'none',
inherit => 0,
},
'min-height' => {
format => '<length>|<percentage>|none',
default => 'none',
inherit => 0,
},
'min-width' => {
format => '<length>|<percentage>|none',
default => 'none',
inherit => 0,
},
orphans => {
format => '<integer>',
default => 2,
inherit => 1,
},
'outline-color' => {
format => '<colour>|invert',
default => 'invert',
inherit => 0,
},
'outline-style' => {
format => 'none|hidden|dotted|dashed|solid|double|groove|ridge|
inset|outset',
default => 'none',
inherit => 0,
},
'outline-width' => {
format => '<length>|thin|thick|medium',
default => 'medium',
inherit => 0,
},
outline => {
format => "'outline-color'||'outline-style'||'outline-width'",
serialise => sub {
my $p = shift;
my $ret = '';
for(qw/ color style width /) {
length $p->{"outline-$_"}
and $ret .= $p->{"outline-$_"}." ";
}
chop $ret;
length $ret ? $ret : 'invert';
},
},
overflow => {
format => 'visible|hidden|scroll|auto',
default => 'visible',
inherit => 0,
},
the min-/max- quad all default to 'none' and inherit => 0, simple, clean, slay. orphans is a lonely <integer> with default => 2 and inherit => 1 (widows is coming later, they're a set, don't worry πΈ).
outline is the shorthand for outline-color / outline-style / outline-width, and its serialise uses length (not just truthiness) to decide whether to append each one, chops the trailing space, and if nothing's left it returns 'invert'. note that outline-color's own default is 'invert' β they match, cute. π and overflow is just visible|hidden|scroll|auto, default => 'visible', inherit => 0. periodt.
πΈ padding & page-breaks & pause πΈ
'padding-top' => {
format => '<length>|<percentage>',
default => 0,
inherit => 0,
},
'padding-right' => {
format => '<length>|<percentage>',
default => 0,
inherit => 0,
},
'padding-bottom' => {
format => '<length>|<percentage>',
default => 0,
inherit => 0,
},
'padding-left' => {
format => '<length>|<percentage>',
default => 0,
inherit => 0,
},
padding => {
format => "(<length>|<percentage>)
[ (<length>|<percentage>)
[ (<length>|<percentage>)
(<length>|<percentage>)?
]?
]?",
properties => {
'padding-top' => [1],
'padding-right' => [2,1],
'padding-bottom' => [3,1],
'padding-left' => [4,2,1],
},
serialise => sub {
my $p = shift;
my @vals = map $p->{"padding-$_"},
qw/top right bottom left/;
$vals[3] eq $vals[1] and pop @vals,
$vals[2] eq $vals[0] and pop @vals,
$vals[1] eq $vals[0] and pop @vals;
return join " ", map $_ || 0, @vals;
},
},
'page-break-after' => {
format => 'auto|always|avoid|left|right',
default => 'auto',
inherit => 0,
},
'page-break-before' => {
format => 'auto|always|avoid|left|right',
default => 'auto',
inherit => 0,
},
'page-break-inside' => {
format => 'avoid|auto',
default => 'auto',
inherit => 1,
},
'pause-after' => {
format => '<time>|<percentage>',
default => 0,
inherit => 0,
},
'pause-before' => {
format => '<time>|<percentage>',
default => 0,
inherit => 0,
},
pause => {
format => '(<time>|<percentage>)(<time>|<percentage>)?',
properties => {
'pause-before' => [1],
'pause-after' => [2,1],
}
},
okay so padding is a carbon copy of margin β same nested-optional format, same properties slot map ([1], [2,1], [3,1], [4,2,1]), same pop-the-duplicates serialise with qw/top right bottom left/ and map $_ || 0, @vals. the only diff? no auto in the format and default => 0. consistency is literally everything. β¨
then the page-break trio: -after and -before both do auto|always|avoid|left|right with default => 'auto' and inherit => 0, while -inside is the odd one out at avoid|auto, default => 'auto', but inherit => 1. noted. π
pause-after and pause-before each take <time>|<percentage>, default => 0, inherit => 0. and the pause shorthand? it's got a properties map ('pause-before' => [1], 'pause-after' => [2,1]) but no serialise and no default β that's it, that's the whole entry. π
'pitch-range' => {
format => '<number>',
default => 50,
inherit => 1,
},
pitch => {
format => '<frequency>|x-low|low|medium|high|x-high',
default => 'medium',
inherit => 1,
},
'play-during' => {
format => '<url> [ mix || repeat ]? | auto | none',
default => 'auto',
inherit => 0,
},
position => {
format => 'static|relative|absolute|fixed',
default => 'relative',
inherit => 0,
},
quotes => {
format => '[(<string>)(<string>)]+|none',
default => 'none',
inherit => 1,
list => 1,
},
richness => {
format => '<number>',
default => 50,
inherit => 1,
},
right => {
format => '<length>|<percentage>|auto',
default => 'auto',
inherit => 0,
},
the audio/aural girlies showing up: pitch-range and richness are both <number> with default => 50 and inherit => 1 (twins π), pitch is <frequency>|x-low|low|medium|high|x-high defaulting to 'medium', and play-during is the fancy one: '<url> [ mix || repeat ]? | auto | none' with default => 'auto' and inherit => 0. π§
position defaults to 'relative' (yes, static|relative|absolute|fixed, and the default is relative, that's what it says, don't @ me). quotes is [(<string>)(<string>)]+|none, default => 'none', inherit => 1, and it's flagged list => 1 so it can hold multiple values. and right is the mirror of left β <length>|<percentage>|auto, default => 'auto', inherit => 0. π¦
π speak, text, and friends π
'speak-header' => {
format => 'once|always',
default => 'once',
inherit => 1,
},
'speak-numeral' => {
format => 'digits|continuous',
default => 'continuous',
inherit => 1,
},
'speak-punctuation' => {
format => 'code|none',
default => 'none',
inherit => 1,
},
speak => {
format => 'normal|none|spell-out',
default => 'normal',
inherit => 1,
},
'speech-rate' => {
format => '<number>|x-slow|slow|medium|fast|x-fast|faster|slower',
default => 'medium',
inherit => 1,
},
stress => {
format => '<number>',
default => 50,
inherit => 1,
},
'table-layout' => {
format => 'auto|fixed',
default => 'auto',
inherit => 0,
},
the whole speak-* squad inherits (inherit => 1) β speak-header is once|always (default 'once'), speak-numeral is digits|continuous (default 'continuous'), speak-punctuation is code|none (default 'none'), and plain speak is normal|none|spell-out (default 'normal'). speech-rate is that long list <number>|x-slow|slow|medium|fast|x-fast|faster|slower defaulting to 'medium'. and stress is another <number> at default => 50 β matching pitch-range and richness exactly, trio of 50s, we love to see it. π
table-layout is chill: auto|fixed, default => 'auto', inherit => 0.
'text-align' => {
format => 'left|right|center|justify|auto',
default => 'auto',
inherit => 1,
},
'text-decoration' => {
format => 'none | underline||overline||line-through||blink ',
default => 'none',
inherit => 0,
},
'text-indent' => {
format => '<length>|<percentage>',
default => 0,
inherit => 1,
},
'text-transform' => {
format => 'capitalize|uppercase|lowercase|none',
default => 'none',
inherit => 1,
},
top => {
format => '<length>|<percentage>|auto',
default => 'auto',
inherit => 0,
},
'unicode-bidi' => {
format => 'normal|embed|bidi-override',
default => 'normal',
inherit => 0,
},
'vertical-align' => {
format => 'baseline|sub|super|top|text-top|middle|bottom|
text-bottom|<percentage>|<length>',
default => 'baseline',
inherit => 0,
},
visibility => {
format => 'visible|hidden|collapse',
default => 'visible',
inherit => 1,
},
text-align is left|right|center|justify|auto, default => 'auto', inherit => 1. text-decoration is the one with the double-pipes β 'none | underline||overline||line-through||blink ' (yes there's a trailing space in that string, keep it, it's canon) with default => 'none' and inherit => 0. text-indent is <length>|<percentage>, default => 0, inherit => 1. text-transform is capitalize|uppercase|lowercase|none, default => 'none', inherit => 1. the way that they all start with text- and I'm obsessed. π
top mirrors right and left (<length>|<percentage>|auto, default => 'auto', inherit => 0). unicode-bidi is normal|embed|bidi-override, default => 'normal', inherit => 0. vertical-align is that long one β baseline|sub|super|top|text-top|middle|bottom|text-bottom|<percentage>|<length>, default => 'baseline', inherit => 0. and visibility is visible|hidden|collapse, default => 'visible', inherit => 1.
'voice-family' => {
format => '(male|female|child|<str/words>)
[, (male|female|child|<str/words>) ]*',
default => '',
inherit => 1,
list => 1,
},
volume => {
format => '<number>|<percentage>|silent|x-soft|soft|medium|loud|
x-loud',
default => 'medium',
inherit => 1,
},
'white-space' => {
format => 'normal|pre|nowrap|pre-wrap|pre-line',
default => 'normal',
inherit => 1,
},
widows => {
format => '<integer>',
default => 2,
inherit => 1,
},
width => {
format => '<length>|<percentage>|auto',
default => 'auto',
inherit => 0,
},
'word-spacing' => {
format => 'normal|<length>',
default => 'normal',
inherit => 1,
},
voice-family is a list => 1 baddie β (male|female|child|<str/words>) [, (male|female|child|<str/words>) ]*, default => '', inherit => 1. volume is '<number>|<percentage>|silent|x-soft|soft|medium|loud|x-loud', default => 'medium', inherit => 1. note 'white-space' has a lil extra whitespace before its { ('white-space' => {) β yes it's 'normal|pre|nowrap|pre-wrap|pre-line', default => 'normal', inherit => 1.
widows finally shows up to complete the orphans/widows duo β <integer>, default => 2, inherit => 1, matching her sister perfectly. width is <length>|<percentage>|auto, default => 'auto', inherit => 0. and word-spacing closes this stretch: normal|<length>, default => 'normal', inherit => 1. π
and that's the table so far β but bestie we are not done, not even close, there's more of this file to unbox so keep scrolling and I'll see you in the next bit π
'z-index' => {
format => 'auto|<integer>',
default => 'auto',
inherit => 0,
},
);
$CSS21->add_property( $_ => $properties{$_} ) for keys %properties;
okay so z-index is the last one in the list, and bestie look at it β its format is auto|<integer>, its default is auto, and inherit is 0, so it does not inherit. noted. π
and then that final line? that's the glow-up moment β¨ it walks every key in %properties and hands each one straight to $CSS21->add_property( $_ => $properties{$_} ), so the whole property table gets registered on $CSS21 in one single line. every property we just spelled out, all at once, no cap. the way that's efficientβ¦ π₯
π SEE ALSO π
CSS::DOMβ go read this one too, fr fr, it's the main character π«Ά
and that's the whole page, bestie!! π you made it to the end, so proud of you πΈ this one's perl v5.36.0, dated 2023-08-02, and it lives under the name CSS::D...Parser(3pm) in the User Contributed Perl Documentation section π now go write some slay stylesheets, iykyk π¦β¨
π¦ where this page comes from
- debian trixie: libcss-dom-perl 0.17-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libcss-dom-perl 0.17-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libcss-dom-perl 0.17-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libcss-dom-perl 0.17-3 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms