Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.11% covered (warning)
61.11%
22 / 36
27.27% covered (danger)
27.27%
3 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
ForeignAttributes
61.11% covered (warning)
61.11%
22 / 36
27.27% covered (danger)
27.27%
3 / 11
34.00
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 offsetExists
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 offsetSet
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 offsetUnset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getValues
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 count
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIterator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getObjects
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
6.17
 merge
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 clone
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Wikimedia\RemexHtml\TreeBuilder;
4
5use Wikimedia\RemexHtml\HTMLData;
6use Wikimedia\RemexHtml\Tokenizer\Attribute;
7use Wikimedia\RemexHtml\Tokenizer\Attributes;
8
9/**
10 * An Attributes class for storing attributes on foreign elements, which may
11 * have namespaces. Features lazy adjustment of attribute name case.
12 */
13class ForeignAttributes implements Attributes {
14    /** @var Attributes */
15    protected $unadjusted;
16
17    /** @var array The map of lowercase attribute name to correct attribute name */
18    protected $table;
19
20    /** @var Attribute[]|null */
21    protected $attrObjects;
22
23    /**
24     * Adjustment tables for the case of attributes on MathML and SVG elements
25     * @var array
26     */
27    private static $adjustmentTables = [
28        'math' => [
29            'definitionurl' => 'definitionURL',
30        ],
31        'svg' => [
32            'attributename' => 'attributeName',
33            'attributetype' => 'attributeType',
34            'basefrequency' => 'baseFrequency',
35            'baseprofile' => 'baseProfile',
36            'calcmode' => 'calcMode',
37            'clippathunits' => 'clipPathUnits',
38            'diffuseconstant' => 'diffuseConstant',
39            'edgemode' => 'edgeMode',
40            'filterunits' => 'filterUnits',
41            'glyphref' => 'glyphRef',
42            'gradienttransform' => 'gradientTransform',
43            'gradientunits' => 'gradientUnits',
44            'kernelmatrix' => 'kernelMatrix',
45            'kernelunitlength' => 'kernelUnitLength',
46            'keypoints' => 'keyPoints',
47            'keysplines' => 'keySplines',
48            'keytimes' => 'keyTimes',
49            'lengthadjust' => 'lengthAdjust',
50            'limitingconeangle' => 'limitingConeAngle',
51            'markerheight' => 'markerHeight',
52            'markerunits' => 'markerUnits',
53            'markerwidth' => 'markerWidth',
54            'maskcontentunits' => 'maskContentUnits',
55            'maskunits' => 'maskUnits',
56            'numoctaves' => 'numOctaves',
57            'pathlength' => 'pathLength',
58            'patterncontentunits' => 'patternContentUnits',
59            'patterntransform' => 'patternTransform',
60            'patternunits' => 'patternUnits',
61            'pointsatx' => 'pointsAtX',
62            'pointsaty' => 'pointsAtY',
63            'pointsatz' => 'pointsAtZ',
64            'preservealpha' => 'preserveAlpha',
65            'preserveaspectratio' => 'preserveAspectRatio',
66            'primitiveunits' => 'primitiveUnits',
67            'refx' => 'refX',
68            'refy' => 'refY',
69            'repeatcount' => 'repeatCount',
70            'repeatdur' => 'repeatDur',
71            'requiredextensions' => 'requiredExtensions',
72            'requiredfeatures' => 'requiredFeatures',
73            'specularconstant' => 'specularConstant',
74            'specularexponent' => 'specularExponent',
75            'spreadmethod' => 'spreadMethod',
76            'startoffset' => 'startOffset',
77            'stddeviation' => 'stdDeviation',
78            'stitchtiles' => 'stitchTiles',
79            'surfacescale' => 'surfaceScale',
80            'systemlanguage' => 'systemLanguage',
81            'tablevalues' => 'tableValues',
82            'targetx' => 'targetX',
83            'targety' => 'targetY',
84            'textlength' => 'textLength',
85            'viewbox' => 'viewBox',
86            'viewtarget' => 'viewTarget',
87            'xchannelselector' => 'xChannelSelector',
88            'ychannelselector' => 'yChannelSelector',
89            'zoomandpan' => 'zoomAndPan',
90        ],
91        'other' => [],
92    ];
93
94    /**
95     * The potentially namespaced attributes, and the namespaces they belong to.
96     * Excepting xmlns since it is very special.
97     *
98     * @var array
99     */
100    private static $namespaceMap = [
101        'xlink:actuate' => HTMLData::NS_XLINK,
102        'xlink:arcrole' => HTMLData::NS_XLINK,
103        'xlink:href' => HTMLData::NS_XLINK,
104        'xlink:role' => HTMLData::NS_XLINK,
105        'xlink:show' => HTMLData::NS_XLINK,
106        'xlink:title' => HTMLData::NS_XLINK,
107        'xlink:type' => HTMLData::NS_XLINK,
108        'xml:lang' => HTMLData::NS_XML,
109        'xml:space' => HTMLData::NS_XML,
110        'xmlns:xlink' => HTMLData::NS_XMLNS,
111    ];
112
113    /**
114     * @param Attributes $unadjusted The unadjusted attributes from the Tokenizer
115     * @param string $type The element type, which may be "math", "svg" or "other".
116     */
117    public function __construct( Attributes $unadjusted, $type ) {
118        $this->unadjusted = $unadjusted;
119        $this->table = self::$adjustmentTables[$type];
120    }
121
122    public function offsetExists( $offset ): bool {
123        $offset = $this->table[$offset] ?? $offset;
124        return $this->unadjusted->offsetExists( $offset );
125    }
126
127    public function &offsetGet( $offset ): string {
128        $offset = $this->table[$offset] ?? $offset;
129        $value = &$this->unadjusted->offsetGet( $offset );
130        return $value;
131    }
132
133    public function offsetSet( $offset, $value ): void {
134        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
135        throw new TreeBuilderError( "Setting foreign attributes is not supported" );
136    }
137
138    public function offsetUnset( $offset ): void {
139        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
140        throw new TreeBuilderError( "Setting foreign attributes is not supported" );
141    }
142
143    public function getValues() {
144        $result = [];
145        foreach ( $this->unadjusted->getValues() as $name => $value ) {
146            $name = $this->table[$name] ?? $name;
147            $result[$name] = $value;
148        }
149        return $result;
150    }
151
152    public function count(): int {
153        return $this->unadjusted->count();
154    }
155
156    public function getIterator(): \ArrayIterator {
157        return new \ArrayIterator( $this->getValues() );
158    }
159
160    public function getObjects() {
161        if ( $this->attrObjects === null ) {
162            $result = [];
163            foreach ( $this->unadjusted->getValues() as $name => $value ) {
164                if ( isset( $this->table[$name] ) ) {
165                    $name = $this->table[$name];
166                }
167                if ( $name === 'xmlns' ) {
168                    $prefix = null;
169                    $namespace = HTMLData::NS_XMLNS;
170                    $localName = $name;
171                } elseif ( isset( self::$namespaceMap[$name] ) ) {
172                    $namespace = self::$namespaceMap[$name];
173                    [ $prefix, $localName ] = explode( ':', $name, 2 );
174                } else {
175                    $prefix = null;
176                    $namespace = null;
177                    $localName = $name;
178                }
179                $result[$name] = new Attribute( $name, $namespace, $prefix, $localName, $value );
180            }
181            $this->attrObjects = $result;
182        }
183        return $this->attrObjects;
184    }
185
186    public function merge( Attributes $other ) {
187        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
188        throw new TreeBuilderError( __METHOD__ . ': unimplemented' );
189    }
190
191    public function clone() {
192        return $this;
193    }
194}