/[drupal]/contributions/modules/fquery/fquery.module
ViewVC logotype

Contents of /contributions/modules/fquery/fquery.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Fri Nov 24 02:36:44 2006 UTC (3 years ago) by unconed
Branch: MAIN
CVS Tags: HEAD
File MIME type: text/x-php
Initial commit of fQuery module, which makes handling form API arrays easier. see http://acko.net/dev/fquery.
1 <?php
2 // $Id$
3
4 // String
5 define('FQUERY_REGEXP_S', '([a-z*_-][a-z0-9_-]*)');
6
7 // ID String
8 define('FQUERY_REGEXP_I', '([#]?[a-z*_-][a-z0-9_-]*)');
9
10 // Quoted value
11 define('FQUERY_REGEXP_Q', '\ *\'?"?([^\'"]*?)\'?"?\ *');
12
13 // Regexp for an entire selector
14 define('FQUERY_REGEXP_FULL', '/
15 \ *'. FQUERY_REGEXP_S .'?
16 (
17 \.'. FQUERY_REGEXP_S .' |
18 \#'. FQUERY_REGEXP_S .' |
19 \[\ *'. FQUERY_REGEXP_I .'\ *([!*$^=~]*)\ *'. FQUERY_REGEXP_Q .'\] |
20 :'. FQUERY_REGEXP_S .'\('. FQUERY_REGEXP_Q .'\) |
21 :'. FQUERY_REGEXP_S .'
22 )*
23 \ *([>+]?)\ *
24 /xi');
25
26 // Regexp for a single selector token
27 define('FQUERY_REGEXP_TOKEN', '/
28 \ *'. FQUERY_REGEXP_S .' |
29 \.'. FQUERY_REGEXP_S .' |
30 \#'. FQUERY_REGEXP_S .' |
31 \[\ *'. FQUERY_REGEXP_I .'\ *([!*$^=~]*)\ *'. FQUERY_REGEXP_Q .'\] |
32 :'. FQUERY_REGEXP_S .'\('. FQUERY_REGEXP_Q .'\) |
33 :'. FQUERY_REGEXP_S .'
34 /xi');
35
36 /**
37 * Perform a query on a form array, using the given selector.
38 *
39 * @param $selector
40 * A CSS-like selector string.
41 * @param $form
42 * The form or element to query. Must have been passed through form_builder() before.
43 * @return
44 * An array which contains references to the matching elements.
45 */
46 function f($selector, &$form) {
47 // Parse the complete selector
48
49 // Split up concatenations
50 $selectors = explode(',', $selector);
51 $result = array();
52 foreach ($selectors as $selector) {
53 // Reset accumulator.
54 $recurse = true;
55 $accumulator = array();
56
57 // Define form to search.
58 $search = array(&$form);
59
60 // Match simple selectors and their combinator.
61 // e.g. "fieldset >" "textarea.resizable"
62 preg_match_all(FQUERY_REGEXP_FULL, trim($selector), $matches, PREG_SET_ORDER);
63 if (count($matches) == 0) {
64 return array();
65 }
66 foreach ($matches as $match) {
67 if (strlen(trim($match[0])) == 0) {
68 continue;
69 }
70
71 // Split simple selector into tokens.
72 // e.g. "textarea" ":first-child" ".resizable"
73 $tokens = _f_tokenize($match[0]);
74
75 // Apply selector tokens to the search space.
76 foreach ($search as &$element) {
77 _f_apply($tokens, $accumulator, $element, $names, $recurse);
78 }
79
80 // Apply relations.
81 switch ($match[11]) {
82 case '+':
83 case '~':
84 // Adjacency and siblings. Make a virtual parent for this set, to recurse from.
85 $old = $accumulator;
86 $accumulator = array('__virtual' => array());
87 // Replace the search space by the siblings of the result set.
88 foreach (element_children($old) as $name) {
89 $element = &$old[$name];
90 $found = false;
91 // Select all siblings
92 foreach (element_children($element['#parent']) as $sname) {
93 $sibling = &$element['#parent'][$sname];
94 // This is the first 'next' element.
95 if ($found) {
96 $accumulator['__virtual'][$sibling['#guid']] = &$sibling;
97 // Only adjacent?
98 if ($match[11] == '+') {
99 break;
100 }
101 }
102 // See if we've found this element.
103 if (!$found && $element['#guid'] == $sibling['#guid']) {
104 $found = true;
105 }
106 }
107 }
108 // Search this set only (descendants of the virtual parent)
109 $recurse = false;
110 break;
111 case '>':
112 // Direct descendants only
113 $recurse = false;
114 default:
115 // All descendants
116 $recurse = true;
117 break;
118 }
119
120 // Go to next selector.
121 $search = $accumulator;
122 $accumulator = array();
123 }
124
125 // Merge accumulator into result set.
126 if (!isset($search['__virtual'])) {
127 foreach ($search as &$element) {
128 $result[$element['#guid']] = &$element;
129 }
130 }
131 }
132 return array_values($result);
133 }
134
135 /**
136 * Tokenize a simple CSS selector.
137 */
138 function _f_tokenize($string) {
139 $tokens = array();
140 // Split into component steps.
141 preg_match_all(FQUERY_REGEXP_TOKEN, $string, $matches, PREG_SET_ORDER);
142 foreach ($matches as $token) {
143 // See if we found something meaningful.
144 if (strlen(trim($token[0]))) {
145 // Trim all individual components.
146 foreach ($token as $k => $v) {
147 $token[$k] = trim($v);
148 }
149 $tokens[] = $token;
150 }
151 }
152 return $tokens;
153 }
154
155 /**
156 * Apply a single level of a CSS selector to the given form.
157 */
158 function _f_apply(&$tokens, &$accumulator, &$form, &$names, $recurse = TRUE) {
159 // Iterate over children.
160 $i = 0;
161 foreach (element_children($form) as $name) {
162 $element = &$form[$name];
163 // Set GUID.
164 if (!isset($element['#guid'])) {
165 static $guid = 0;
166 $element['#guid'] = $guid++ .'-'. $name;
167 }
168 // Set index.
169 if (!isset($element['#index'])) {
170 $element['#index'] = $i++;
171 }
172 // Set parent reference.
173 if (!isset($element['#parent'])) {
174 $element['#parent'] = &$form;
175 }
176 // Set id.
177 if (!isset($element['#array_key'])) {
178 $element['#array_key'] = $name;
179 }
180
181 // See if the element matches.
182 if (_f_match_element($tokens, $element)) {
183 $accumulator[$element['#guid']] = &$element;
184 }
185
186 // Recurse to children.
187 if ($recurse) {
188 _f_apply($tokens, $accumulator, $element, $names);
189 }
190 }
191 }
192
193 /**
194 * See if an element matches a given set of tokens.
195 */
196 function _f_match_element(&$tokens, &$element) {
197 foreach ($tokens as $token) {
198 if (!_f_match_token($token, $element)) {
199 return false;
200 }
201 }
202 return true;
203 }
204
205 /**
206 * See if an element matches a given token.
207 */
208 function _f_match_token(&$token, &$element) {
209 // Check field type
210 if ($token[1] != '' && $token[1] != '*' && $element['#type'] != $token[1]) {
211 return false;
212 }
213 // Check field class (actually binary attributes)
214 if ($token[2] != '' && !$element['#'. $token[2]]) {
215 return false;
216 }
217 // Check field id (actually name)
218 if ($token[3] != '') {
219 return $element['#array_key'] == $token[3];
220 }
221 // Check attribute selector/matching
222 if ($token[4] != '') {
223 // Unified syntax for direct form API attributes #foo => bar
224 // and HTML attributes #attributes => array('foo' => 'bar')
225 $attrib = '';
226 if ($token[4][0] == '#' && isset($element[$token[4]])) {
227 $attrib = &$element[$token[4]];
228 } else if (isset($element['#attributes']) && isset($element['#attributes'][$token[4]])) {
229 $attrib = &$element['#attributes'][$token[4]];
230 }
231
232 // Apply rules
233 switch ($token[5]) {
234 case "=": // Equals
235 return $attrib == $token[6];
236 case "!=": // Not equals
237 return $attrib != $token[6];
238 case "^=": // Starts with
239 $p = strpos($attrib, $token[6]);
240 return $p === 0;
241 case "$=": // Ends with
242 $p = strpos($attrib, $token[6]);
243 return ($p !== FALSE) && ($p == strlen($attrib) - strlen($token[6]));
244 case "*=": // Contains
245 $p = strpos($attrib, $token[6]);
246 return ($p !== FALSE);
247 case "~=": // Contains space-separated token
248 return in_array($token[6], explode(' ', $attrib));
249 default: // Is set
250 return trim($attrib) != '';
251 }
252 }
253
254 // Various operators with and without arguments
255 if ($token[7] != '') {
256 $token[9] = $token[7];
257 }
258 if ($token[9]) {
259 switch ($token[9]) {
260 // Structural checks
261 case 'nth-child':
262 return $element['#index'] == $token[8];
263 case 'nth-last-child':
264 return $element['#index'] == count(element_children($element['#parent'])) - 1 - $token[8];
265 case 'first-child':
266 return $element['#index'] == 0;
267 case 'last-child':
268 return $element['#index'] == count(element_children($element['#parent'])) - 1;
269 case 'only-child':
270 return count(element_children($element['#parent'])) == 1;
271 case 'empty':
272 return count(element_children($element['#parent']));
273 case 'not':
274 return !_f_match_element(_f_tokenize($token[8]), $element);
275 }
276 }
277
278 return true;
279 }
280

  ViewVC Help
Powered by ViewVC 1.1.2