1<?php
2
3/**
4 * Represents a measurable length, with a string numeric magnitude
5 * and a unit. This object is immutable.
6 */
7class HTMLPurifier_Length
8{
9
10    /**
11     * String numeric magnitude.
12     * @type string
13     */
14    protected $n;
15
16    /**
17     * String unit. False is permitted if $n = 0.
18     * @type string|bool
19     */
20    protected $unit;
21
22    /**
23     * Whether or not this length is valid. Null if not calculated yet.
24     * @type bool
25     */
26    protected $isValid;
27
28    /**
29     * Array Lookup array of units recognized by CSS 3
30     * @type array
31     */
32    protected static $allowedUnits = array(
33        'em' => true, 'ex' => true, 'px' => true, 'in' => true,
34        'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true,
35        'ch' => true, 'rem' => true, 'vw' => true, 'vh' => true,
36        'vmin' => true, 'vmax' => true
37    );
38
39    /**
40     * @param string $n Magnitude
41     * @param bool|string $u Unit
42     */
43    public function __construct($n = '0', $u = false)
44    {
45        $this->n = (string) $n;
46        $this->unit = $u !== false ? (string) $u : false;
47    }
48
49    /**
50     * @param string $s Unit string, like '2em' or '3.4in'
51     * @return HTMLPurifier_Length
52     * @warning Does not perform validation.
53     */
54    public static function make($s)
55    {
56        if ($s instanceof HTMLPurifier_Length) {
57            return $s;
58        }
59        $n_length = strspn($s, '1234567890.+-');
60        $n = substr($s, 0, $n_length);
61        $unit = substr($s, $n_length);
62        if ($unit === '') {
63            $unit = false;
64        }
65        return new HTMLPurifier_Length($n, $unit);
66    }
67
68    /**
69     * Validates the number and unit.
70     * @return bool
71     */
72    protected function validate()
73    {
74        // Special case:
75        if ($this->n === '+0' || $this->n === '-0') {
76            $this->n = '0';
77        }
78        if ($this->n === '0' && $this->unit === false) {
79            return true;
80        }
81        if ($this->unit === false || !ctype_lower($this->unit)) {
82            $this->unit = strtolower($this->unit);
83        }
84        if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) {
85            return false;
86        }
87        // Hack:
88        $def = new HTMLPurifier_AttrDef_CSS_Number();
89        $result = $def->validate($this->n, false, false);
90        if ($result === false) {
91            return false;
92        }
93        $this->n = $result;
94        return true;
95    }
96
97    /**
98     * Returns string representation of number.
99     * @return string
100     */
101    public function toString()
102    {
103        if (!$this->isValid()) {
104            return false;
105        }
106        return $this->n . $this->unit;
107    }
108
109    /**
110     * Retrieves string numeric magnitude.
111     * @return string
112     */
113    public function getN()
114    {
115        return $this->n;
116    }
117
118    /**
119     * Retrieves string unit.
120     * @return string
121     */
122    public function getUnit()
123    {
124        return $this->unit;
125    }
126
127    /**
128     * Returns true if this length unit is valid.
129     * @return bool
130     */
131    public function isValid()
132    {
133        if ($this->isValid === null) {
134            $this->isValid = $this->validate();
135        }
136        return $this->isValid;
137    }
138
139    /**
140     * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
141     * @param HTMLPurifier_Length $l
142     * @return int
143     * @warning If both values are too large or small, this calculation will
144     *          not work properly
145     */
146    public function compareTo($l)
147    {
148        if ($l === false) {
149            return false;
150        }
151        if ($l->unit !== $this->unit) {
152            $converter = new HTMLPurifier_UnitConverter();
153            $l = $converter->convert($l, $this->unit);
154            if ($l === false) {
155                return false;
156            }
157        }
158        return $this->n - $l->n;
159    }
160}
161
162// vim: et sw=4 sts=4
163