1<?php
2
3/**
4 * Structure that stores an HTML element definition. Used by
5 * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
6 * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
7 *       Please update that class too.
8 * @warning If you add new properties to this class, you MUST update
9 *          the mergeIn() method.
10 */
11class HTMLPurifier_ElementDef
12{
13    /**
14     * Does the definition work by itself, or is it created solely
15     * for the purpose of merging into another definition?
16     * @type bool
17     */
18    public $standalone = true;
19
20    /**
21     * Associative array of attribute name to HTMLPurifier_AttrDef.
22     * @type array
23     * @note Before being processed by HTMLPurifier_AttrCollections
24     *       when modules are finalized during
25     *       HTMLPurifier_HTMLDefinition->setup(), this array may also
26     *       contain an array at index 0 that indicates which attribute
27     *       collections to load into the full array. It may also
28     *       contain string indentifiers in lieu of HTMLPurifier_AttrDef,
29     *       see HTMLPurifier_AttrTypes on how they are expanded during
30     *       HTMLPurifier_HTMLDefinition->setup() processing.
31     */
32    public $attr = array();
33
34    // XXX: Design note: currently, it's not possible to override
35    // previously defined AttrTransforms without messing around with
36    // the final generated config. This is by design; a previous version
37    // used an associated list of attr_transform, but it was extremely
38    // easy to accidentally override other attribute transforms by
39    // forgetting to specify an index (and just using 0.)  While we
40    // could check this by checking the index number and complaining,
41    // there is a second problem which is that it is not at all easy to
42    // tell when something is getting overridden. Combine this with a
43    // codebase where this isn't really being used, and it's perfect for
44    // nuking.
45
46    /**
47     * List of tags HTMLPurifier_AttrTransform to be done before validation.
48     * @type array
49     */
50    public $attr_transform_pre = array();
51
52    /**
53     * List of tags HTMLPurifier_AttrTransform to be done after validation.
54     * @type array
55     */
56    public $attr_transform_post = array();
57
58    /**
59     * HTMLPurifier_ChildDef of this tag.
60     * @type HTMLPurifier_ChildDef
61     */
62    public $child;
63
64    /**
65     * Abstract string representation of internal ChildDef rules.
66     * @see HTMLPurifier_ContentSets for how this is parsed and then transformed
67     * into an HTMLPurifier_ChildDef.
68     * @warning This is a temporary variable that is not available after
69     *      being processed by HTMLDefinition
70     * @type string
71     */
72    public $content_model;
73
74    /**
75     * Value of $child->type, used to determine which ChildDef to use,
76     * used in combination with $content_model.
77     * @warning This must be lowercase
78     * @warning This is a temporary variable that is not available after
79     *      being processed by HTMLDefinition
80     * @type string
81     */
82    public $content_model_type;
83
84    /**
85     * Does the element have a content model (#PCDATA | Inline)*? This
86     * is important for chameleon ins and del processing in
87     * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
88     * have to worry about this one.
89     * @type bool
90     */
91    public $descendants_are_inline = false;
92
93    /**
94     * List of the names of required attributes this element has.
95     * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
96     * @type array
97     */
98    public $required_attr = array();
99
100    /**
101     * Lookup table of tags excluded from all descendants of this tag.
102     * @type array
103     * @note SGML permits exclusions for all descendants, but this is
104     *       not possible with DTDs or XML Schemas. W3C has elected to
105     *       use complicated compositions of content_models to simulate
106     *       exclusion for children, but we go the simpler, SGML-style
107     *       route of flat-out exclusions, which correctly apply to
108     *       all descendants and not just children. Note that the XHTML
109     *       Modularization Abstract Modules are blithely unaware of such
110     *       distinctions.
111     */
112    public $excludes = array();
113
114    /**
115     * This tag is explicitly auto-closed by the following tags.
116     * @type array
117     */
118    public $autoclose = array();
119
120    /**
121     * If a foreign element is found in this element, test if it is
122     * allowed by this sub-element; if it is, instead of closing the
123     * current element, place it inside this element.
124     * @type string
125     */
126    public $wrap;
127
128    /**
129     * Whether or not this is a formatting element affected by the
130     * "Active Formatting Elements" algorithm.
131     * @type bool
132     */
133    public $formatting;
134
135    /**
136     * Low-level factory constructor for creating new standalone element defs
137     */
138    public static function create($content_model, $content_model_type, $attr)
139    {
140        $def = new HTMLPurifier_ElementDef();
141        $def->content_model = $content_model;
142        $def->content_model_type = $content_model_type;
143        $def->attr = $attr;
144        return $def;
145    }
146
147    /**
148     * Merges the values of another element definition into this one.
149     * Values from the new element def take precedence if a value is
150     * not mergeable.
151     * @param HTMLPurifier_ElementDef $def
152     */
153    public function mergeIn($def)
154    {
155        // later keys takes precedence
156        foreach ($def->attr as $k => $v) {
157            if ($k === 0) {
158                // merge in the includes
159                // sorry, no way to override an include
160                foreach ($v as $v2) {
161                    $this->attr[0][] = $v2;
162                }
163                continue;
164            }
165            if ($v === false) {
166                if (isset($this->attr[$k])) {
167                    unset($this->attr[$k]);
168                }
169                continue;
170            }
171            $this->attr[$k] = $v;
172        }
173        $this->_mergeAssocArray($this->excludes, $def->excludes);
174        $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
175        $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
176
177        if (!empty($def->content_model)) {
178            $this->content_model =
179                str_replace("#SUPER", $this->content_model, $def->content_model);
180            $this->child = false;
181        }
182        if (!empty($def->content_model_type)) {
183            $this->content_model_type = $def->content_model_type;
184            $this->child = false;
185        }
186        if (!is_null($def->child)) {
187            $this->child = $def->child;
188        }
189        if (!is_null($def->formatting)) {
190            $this->formatting = $def->formatting;
191        }
192        if ($def->descendants_are_inline) {
193            $this->descendants_are_inline = $def->descendants_are_inline;
194        }
195    }
196
197    /**
198     * Merges one array into another, removes values which equal false
199     * @param $a1 Array by reference that is merged into
200     * @param $a2 Array that merges into $a1
201     */
202    private function _mergeAssocArray(&$a1, $a2)
203    {
204        foreach ($a2 as $k => $v) {
205            if ($v === false) {
206                if (isset($a1[$k])) {
207                    unset($a1[$k]);
208                }
209                continue;
210            }
211            $a1[$k] = $v;
212        }
213    }
214}
215
216// vim: et sw=4 sts=4
217