diff --git a/NEWS b/NEWS index 9a80c1ca..db0ba917 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier - Fixed broken numeric entity conversion - Malformed UTF-8 and non-SGML character detection and cleaning implemented - API documentation completed +- Shorthand CSS properties implemented: font 1.0.0beta, released 2006-08-16 - First public release, most functionality implemented. Notable omissions are: diff --git a/TODO b/TODO index 3643491a..9d3cc436 100644 --- a/TODO +++ b/TODO @@ -5,7 +5,6 @@ Core: - border-collapse, caption-side, empty-cells, table-layout, vertical-align - background (and friends) - border, border-* - - font - list-style - Implement all non-essential attribute transforms - Microsoft Word HTML cleaning diff --git a/docs/progress.html b/docs/progress.html index 80c6a9b1..a2ffb688 100644 --- a/docs/progress.html +++ b/docs/progress.html @@ -148,7 +148,7 @@ thead th {text-align:left;padding:0.1em;background-color:#EEE;} color<color> floatENUM(left, right, none), May require layout precautions with clear -fontSHORTHAND +fontSHORTHAND font-familyCSS validator may complain if fallback font family not specified font-sizeCOMPOSITE(<absolute-size>, diff --git a/library/HTMLPurifier/AttrDef/Font.php b/library/HTMLPurifier/AttrDef/Font.php new file mode 100644 index 00000000..7ce21b12 --- /dev/null +++ b/library/HTMLPurifier/AttrDef/Font.php @@ -0,0 +1,154 @@ + true, + 'icon' => true, + 'menu' => true, + 'message-box' => true, + 'small-caption' => true, + 'status-bar' => true + ); + + function HTMLPurifier_AttrDef_Font() { + $def = HTMLPurifier_CSSDefinition::instance(); + $this->info['font-style'] = $def->info['font-style']; + $this->info['font-variant'] = $def->info['font-variant']; + $this->info['font-weight'] = $def->info['font-weight']; + $this->info['font-size'] = $def->info['font-size']; + $this->info['line-height'] = $def->info['line-height']; + $this->info['font-family'] = $def->info['font-family']; + } + + function validate($string, $config, &$context) { + + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') return false; + + // check if it's one of the keywords + $lowercase_string = strtolower($string); + if (isset($this->system_fonts[$lowercase_string])) { + return $lowercase_string; + } + + $bits = explode(' ', $string); // bits to process + $stage = 0; // this indicates what we're looking for + $caught = array(); // which stage 0 properties have we caught? + $stage_1 = array('font-style', 'font-variant', 'font-weight'); + $final = ''; // output + + for ($i = 0, $size = count($bits); $i < $size; $i++) { + if ($bits[$i] === '') continue; + switch ($stage) { + + // attempting to catch font-style, font-variant or font-weight + case 0: + foreach ($stage_1 as $validator_name) { + if (isset($caught[$validator_name])) continue; + $r = $this->info[$validator_name]->validate( + $bits[$i], $config, &$context); + if ($r !== false) { + $final .= $r . ' '; + $caught[$validator_name] = true; + break; + } + } + // all three caught, continue on + if (count($caught) >= 3) $stage = 1; + if ($r !== false) break; + + // attempting to catch font-size and perhaps line-height + case 1: + $found_slash = false; + if (strpos($bits[$i], '/') !== false) { + list($font_size, $line_height) = + explode('/', $bits[$i]); + if ($line_height === '') { + // ooh, there's a space after the slash! + $line_height = false; + $found_slash = true; + } + } else { + $font_size = $bits[$i]; + $line_height = false; + } + $r = $this->info['font-size']->validate( + $font_size, $config, &$context); + if ($r !== false) { + $final .= $r; + // attempt to catch line-height + if ($line_height === false) { + // we need to scroll forward + for ($j = $i + 1; $j < $size; $j++) { + if ($bits[$j] === '') continue; + if ($bits[$j] === '/') { + if ($found_slash) { + return false; + } else { + $found_slash = true; + continue; + } + } + $line_height = $bits[$j]; + break; + } + } else { + // slash already found + $found_slash = true; + $j = $i; + } + if ($found_slash) { + $i = $j; + $r = $this->info['line-height']->validate( + $line_height, $config, &$context); + if ($r !== false) { + $final .= '/' . $r; + } + } + $final .= ' '; + $stage = 2; + break; + } + return false; + + // attempting to catch font-family + case 2: + $font_family = + implode(' ', array_slice($bits, $i, $size - $i)); + $r = $this->info['font-family']->validate( + $font_family, $config, &$context); + if ($r !== false) { + $final .= $r . ' '; + // processing completed successfully + return rtrim($final); + } + return false; + } + } + return false; + } + +} + +?> \ No newline at end of file diff --git a/library/HTMLPurifier/CSSDefinition.php b/library/HTMLPurifier/CSSDefinition.php index f1dfad63..9517fdfa 100644 --- a/library/HTMLPurifier/CSSDefinition.php +++ b/library/HTMLPurifier/CSSDefinition.php @@ -8,6 +8,7 @@ require_once 'HTMLPurifier/AttrDef/Percentage.php'; require_once 'HTMLPurifier/AttrDef/Multiple.php'; require_once 'HTMLPurifier/AttrDef/TextDecoration.php'; require_once 'HTMLPurifier/AttrDef/FontFamily.php'; +require_once 'HTMLPurifier/AttrDef/Font.php'; /** * Defines allowed CSS attributes and what their values are. @@ -165,6 +166,10 @@ class HTMLPurifier_CSSDefinition array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300', '400', '500', '600', '700', '800', '900'), false); + // MUST be called after other font properties, as it references + // a CSSDefinition object + $this->info['font'] = new HTMLPurifier_AttrDef_Font(); + } } diff --git a/tests/HTMLPurifier/AttrDef/CSSTest.php b/tests/HTMLPurifier/AttrDef/CSSTest.php index 897c09e4..efe69873 100644 --- a/tests/HTMLPurifier/AttrDef/CSSTest.php +++ b/tests/HTMLPurifier/AttrDef/CSSTest.php @@ -60,6 +60,7 @@ class HTMLPurifier_AttrDef_CSSTest extends HTMLPurifier_AttrDefHarness $this->assertDef('text-decoration:underline;'); $this->assertDef('font-family:sans-serif;'); $this->assertDef('font-family:Gill, \'Times New Roman\', sans-serif;'); + //$this->assertDef('font:12px serif;'); // duplicates $this->assertDef('text-align:right;text-align:left;', diff --git a/tests/HTMLPurifier/AttrDef/FontTest.php b/tests/HTMLPurifier/AttrDef/FontTest.php new file mode 100644 index 00000000..18ce3bae --- /dev/null +++ b/tests/HTMLPurifier/AttrDef/FontTest.php @@ -0,0 +1,36 @@ +def = new HTMLPurifier_AttrDef_Font(); + + // hodgepodge of usage cases from W3C spec, but " -> ' + $this->assertDef('12px/14px sans-serif'); + $this->assertDef('80% sans-serif'); + $this->assertDef('x-large/110% \'New Century Schoolbook\', serif'); + $this->assertDef('bold italic large Palatino, serif'); + $this->assertDef('normal small-caps 120%/120% fantasy'); + $this->assertDef('300 italic 1.3em/1.7em \'FB Armada\', sans-serif'); + $this->assertDef('600 9px Charcoal'); + $this->assertDef('600 9px/ 12px Charcoal', '600 9px/12px Charcoal'); + + // spacing + $this->assertDef('12px / 14px sans-serif', '12px/14px sans-serif'); + + // system fonts + $this->assertDef('menu'); + + $this->assertDef('800', false); + $this->assertDef('600 9px//12px Charcoal', false); + + } + +} + +?> \ No newline at end of file diff --git a/tests/index.php b/tests/index.php index 2c605fb8..7ea6cf94 100644 --- a/tests/index.php +++ b/tests/index.php @@ -75,6 +75,7 @@ $test_files[] = 'AttrDef/FontFamilyTest.php'; $test_files[] = 'AttrDef/HostTest.php'; $test_files[] = 'AttrDef/IPv4Test.php'; $test_files[] = 'AttrDef/IPv6Test.php'; +$test_files[] = 'AttrDef/FontTest.php'; $test_files[] = 'IDAccumulatorTest.php'; $test_files[] = 'TagTransformTest.php'; $test_files[] = 'AttrTransform/LangTest.php';