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: */
11: class 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", (string)$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: