patch-html_namespacing 0.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,406 @@
1
+ = HTML Namespacing
2
+
3
+ HTML Namespacing automatically adds HTML class attributes to partial HTML
4
+ code.
5
+
6
+ The intent is for HTML, CSS, and JavaScript files to be "namespaced" according
7
+ to their location in the filesystem. That way, CSS and JavaScript can be scoped
8
+ to individual HTML files--even automatically.
9
+
10
+ == Installing
11
+
12
+ gem install adamh-html_namespacing --source http://gems.github.com
13
+
14
+ == Using
15
+
16
+ HTML Namespacing can be used on its own for a snippet of code:
17
+
18
+ require 'rubygems'
19
+ require 'html_namespacing'
20
+
21
+ html = '<p>Here is some <em>HTML</em>!</p>'
22
+
23
+ # Returns '<p class="foo">Here is some <em>HTML</em>!</p>'
24
+ namespaced = HtmlNamespacing::add_namespace_to_html(html, 'foo')
25
+
26
+ == Details
27
+
28
+ Only root tags will be namespaced. For instance, all <tt>p</tt> tags in this
29
+ example will have a class added, but no other tags:
30
+
31
+ <p>This is a root tag. <b>This tag is nested.</b></p>
32
+ <p>This is another root tag.</p>
33
+
34
+ Because XML, DOCTYPE, comments, and <tt>html</tt> tags do not allow the
35
+ <tt>class</tt> attribute, the following HTML will pass through unchanged:
36
+
37
+ <?xml version="1.0"?>
38
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
39
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
40
+ <!-- Here is a comment -->
41
+ <html>
42
+ <head>
43
+ <title>Hello</title>
44
+ </head>
45
+ <body>
46
+ <p>Blah blah blah</p>
47
+ </body>
48
+ </html>
49
+
50
+ (The following elements do not support the <tt>class</tt> attribute:
51
+ <tt>html</tt>, <tt>head</tt>, <tt>base</tt>, <tt>meta</tt>, <tt>title</tt>,
52
+ <tt>link</tt>, <tt>script</tt>, <tt>noscript</tt>, <tt>style</tt>.)
53
+
54
+ Though the actual namespacing functions are written in pure C with no
55
+ dependencies, only Ruby bindings are available at this time.
56
+
57
+ == Integrating in a Rails project
58
+
59
+ HTML namespacing is meant to integrate into a framework. Here is a Rails
60
+ example:
61
+
62
+ In <tt>config/environment.rb</tt>:
63
+
64
+ config.gem 'html_namespacing'
65
+
66
+ In <tt>config/initializers/html_namespacing.rb</tt>:
67
+
68
+ HtmlNamespacing::Plugin::Rails.install
69
+
70
+ Now, all templates will have HTML namespacing applied. For instance, with a
71
+ <tt>views/foos/show.html.erb</tt> like this:
72
+
73
+ <p>
74
+ <b>Name:</b>
75
+ <%=h @foo.name %>
76
+ </p>
77
+
78
+ <%= render(:partial => @foo) %>
79
+
80
+ <%= link_to 'Edit', edit_foo_path(@foo) %> |
81
+ <%= link_to 'Back', foos_path %>
82
+
83
+ The following HTML might be generated (depending on the <tt>_details</tt>
84
+ partial and the data in the actual <tt>Foo</tt> object):
85
+
86
+ <p class="foos-show">
87
+ <b>Name:</b>
88
+ Foo
89
+ </p>
90
+
91
+ <p class="foos-_foo foos-show">
92
+ <b>Description:</b>
93
+ Bar
94
+ </p>
95
+
96
+ <a href="/foos/1/edit" class="hellos-show">Edit</a> |
97
+ <a href="/foos" class="hellos-show">Back</a>
98
+
99
+ == Integrating with Haml and Sass in Rails
100
+
101
+ With Haml:http://haml-lang.com/ and Sass:http://sass-lang.com/ namespacing can
102
+ be automated even further.
103
+
104
+ In your Rails project, implement the following:
105
+
106
+ In <tt>config/environment.rb</tt>:
107
+
108
+ config.gem 'haml', :version => '2.2.0'
109
+ config.gem 'adamh-html_namespacing', :library => 'html_namespacing'
110
+
111
+ In <tt>config/initializers/html_namespacing.rb</tt>:
112
+
113
+ HtmlNamespacing::Plugin::Rails.install
114
+ HtmlNamespacing::Plugin::Sass.install
115
+
116
+ Then add <tt>stylesheet_link_tag(:all, :recursive => true)</tt> to your layout.
117
+
118
+ Now, all templates will have HTML namespacing applied, and Sass files in
119
+ <tt>SASS_DIR/views</tt> (where <tt>SASS_DIR</tt> is Sass's
120
+ <tt>:template_location</tt>, default <tt>public/stylesheets/sass</tt>) will
121
+ also be HTML-namespaced. For example:
122
+
123
+ With a partial, <tt>app/views/foos/show.html.haml</tt> like this:
124
+
125
+ %p
126
+ %strong Name:
127
+ %span.name&= @foo.name
128
+
129
+ = render(:partial => @foo)
130
+
131
+ = link_to('Edit', edit_foo_path(@foo)
132
+ |
133
+ = link_to('Back', foos_path
134
+
135
+ And a Sass file in <tt>public/stylesheets/sass/views/foos/show.sass</tt>:
136
+
137
+ strong
138
+ :font-size 1.3em
139
+ span.name
140
+ :font-style italic
141
+ a&
142
+ :font-weight bold
143
+
144
+ The Sass rules will only apply to their corresponding partial.
145
+
146
+ (Note: to target root-level elements in a Sass partial, use the "&" rule,
147
+ which is standard Sass and will equate to ".NAMESPACE" in this context.)
148
+
149
+ === Options
150
+
151
+ Available options to <tt>HtmlNamespacing::Plugin::Rails.install</tt> are:
152
+
153
+ <tt>:path_to_namespace_callback</tt>: Ruby lambda function which accepts a
154
+ relative path (e.g., "foo/bar") and returns an HTML namespacing string
155
+ (e.g., "foo-bar"). The default is:
156
+
157
+ lambda { |path| path.gsub('/', '-') }
158
+
159
+ If the callback returns <tt>nil</tt>, HTML namespacing will not be applied.
160
+
161
+ <tt>:handle_exception_callback</tt>: Ruby lambda function which accepts
162
+ an <tt>Exception</tt>, an <tt>ActionView::Template</tt>, and an
163
+ <tt>ActionView::Base</tt>. The default behavior is:
164
+
165
+ lambda { |exception, template, view| raise(exception) }
166
+
167
+ If your <tt>:handle_exception_callback</tt> does not raise an exception,
168
+ the template will be rendered as if HtmlNamespacing were not installed.
169
+
170
+ <tt>:javascript</tt>: If set, enable
171
+ <tt>html_namespacing_javascript_tag()</tt> (see below). You will need to
172
+ <tt>include HtmlNamespacing::Plugin::Rails::Helpers</tt> in your helpers to
173
+ gain access to this method.
174
+
175
+ <tt>:javascript_root</tt>: Root of namespaced JavaScript files, if you are
176
+ using <tt>html_namespacing_javascript_tag()</tt> (see below).
177
+
178
+ <tt>:template_formats</tt>: If set (to an Array), apply HTML namespacing
179
+ under the given (String) formats. Default is <tt>['html']</tt>.
180
+
181
+ <tt>:javascript_optional_suffix</tt>: optional suffix for your JavaScript
182
+ files, if you are using <tt>html_namespacing_javascript_tag()</tt>. For
183
+ instance, if <tt>:javascript_optional_suffix</tt> is <tt>'compressed'</tt> and
184
+ you rendered the partial <tt>app/views/foos/_foo.html.haml</tt>, then the
185
+ (presumably minified) file
186
+ <tt>app/javascripts/views/foos/_foo.js.compressed</tt> will be included if it
187
+ exists (otherwise the standard <tt>app/javascripts/views/foos/_foo.js</tt>,
188
+ otherwise nothing).
189
+
190
+ Available options to <tt>HtmlNamespacing::Plugin::Sass.install</tt> are:
191
+
192
+ <tt>:prefix</tt> (default <tt>views</tt>): subdirectory of Sass directory for
193
+ which we want to namespace our Sass.
194
+ <tt>:callback</tt>: See <tt>:template_to_namespace_callback</tt> above; this
195
+ callback is similar, though it accepts a string path rather than an
196
+ <tt>ActionView::Template</tt>.
197
+
198
+ == Why?
199
+
200
+ HTML namespacing gives huge benefits when writing CSS and JavaScript:
201
+ especially if the CSS and JavaScript components are automatically namespaced
202
+ in a similar manner.
203
+
204
+ Imagine we have the following namespaced HTML:
205
+
206
+ <div class="foos-show">
207
+ <p>In the show partial</p>
208
+ <div class="foos-_details">
209
+ <p>In the details partial</p>
210
+ <div class="foos-_description">
211
+ <p>In the description</p>
212
+ </div>
213
+ </div>
214
+ </div>
215
+
216
+ === CSS Namespacing
217
+
218
+ We can set three CSS rules:
219
+
220
+ .foos-show p { font-size: 1.1em; }
221
+ .foos-_details p { font-weight: bold; }
222
+ .foos-_description p { font-style: italic; }
223
+
224
+ In such an example, the top paragraph would be large, the second would be large
225
+ and bold, and the third would be large, bold, and italic.
226
+
227
+ The benefit comes when automating these namespaces. For instance, if the CSS
228
+ rules were placed in <tt>stylesheets/views/foos/show.css</tt>,
229
+ <tt>stylesheets/views/foos/_details.css</tt>, and
230
+ <tt>stylesheets/views/foos/_description.css</tt>, respectively, and a
231
+ preprocessor inserted the namespace before (or within) every rule in the file,
232
+ all CSS namespacing would be done for free, so you would be able to write:
233
+
234
+ <tt>stylesheets/views/foos/show.css</tt>:
235
+
236
+ p { font-size: 1.1em; }
237
+
238
+ <tt>stylesheets/views/foos/_details.css</tt>:
239
+
240
+ p { font-weight: bold; }
241
+
242
+ <tt>stylesheets/views/foos/_description.css</tt>:
243
+
244
+ p { font-style: italic; }
245
+
246
+ Thus, the namespaces would never need to be explicitly mentioned. The
247
+ framework used to generate such CSS is left (for now) as an exercise to the
248
+ reader.
249
+
250
+ === jQuery Namespacing
251
+
252
+ Use the following Rails helper, possibly in your default layout:
253
+
254
+ html_namespacing_javascript_tag(:jquery)
255
+
256
+ Afterwards, similar to our namespaced CSS framework above, you can easily
257
+ namespace JavaScript behavior:
258
+
259
+ <tt>app/javascripts/views/foos/_description.js</tt>:
260
+
261
+ $NS().find('p').click(function() { alert("Yup, you clicked!"); });
262
+
263
+ You just write code specific to one Rails partial without actually writing the
264
+ partial's name anywhere, and you will not need to rewrite any selectors if you
265
+ rename or move the partial (as long as you rename or move the corresponding
266
+ JavaScript file).
267
+
268
+ (All namespaced JavaScript partials will be included inline, all in separate
269
+ <tt>jQuery(document).ready()</tt> blocks. Two local variables will be
270
+ available: <tt>NS</tt>, the namespace string, and <tt>$NS()</tt>, a function
271
+ returning a jQuery object containing all elements at the root of the
272
+ namespace.)
273
+
274
+ In a more complex setup, you may prefer to add another extension, like so:
275
+
276
+ html_namespacing_javascript_tag(:jquery, :format => :mobile)
277
+
278
+ This will find <tt>app/javascripts/views/foos/_description.mobile.js</tt> and
279
+ will ignore <tt>app/javascripts/views/foos/_description.js</tt>. Neglecting the
280
+ second parameter, <tt>html_namespacing_javascript_tag()</tt> would include
281
+ <tt>_description.js</tt> and <em>exclude</em> <tt>_description.mobile.js</tt>:
282
+ only single-extension JavaScript will be included. To include both, use:
283
+
284
+ html_namespacing_javascript_tag(:jquery, :format => [nil, :mobile])
285
+
286
+ === Caching and JavaScript Namespacing
287
+
288
+ Caching throws a wrench into operations.
289
+ <tt>html_namespacing_javascript_tag()</tt> works by watching which templates get
290
+ rendered; when reading from the cache, not all templates will have
291
+ <tt>render()</tt> called on them.
292
+
293
+ The general solution is to cache the list of rendered templates along with the
294
+ data being cached. A new view method, <tt>html_rendering_rendered_paths</tt>,
295
+ returns an Array to be used for this purpose. As a practical example, here is a
296
+ plugin which makes JavaScript namespacing work with
297
+ {cache_advance}[http://github.com/aub/cache_advance]:
298
+
299
+ class HtmlNamespacingCachePlugin
300
+ def self.key_suffix
301
+ 'HTML_NAMESPACING_DATA'
302
+ end
303
+
304
+ def cache_it_options(view)
305
+ { :view => view }
306
+ end
307
+
308
+ def before_render(cache_name, cache_key, options)
309
+ options[:html_namespacing_rendered_paths_length] = paths(options).length
310
+ end
311
+
312
+ def after_render(cache_name, cache_key, data, options)
313
+ old_length = options[:html_namespacing_rendered_paths_length]
314
+ new_length = paths(options).length
315
+
316
+ delta = paths(options)[old_length..new_length]
317
+ unless delta.empty?
318
+ key = cache_key + self.class.key_suffix
319
+ Rails.cache.write(key, delta)
320
+ end
321
+ end
322
+
323
+ def after_read(cache_name, cache_key, data, options)
324
+ key = cache_key + self.class.key_suffix
325
+ if delta = Rails.cache.read(key)
326
+ paths(options).concat(delta)
327
+ end
328
+ end
329
+
330
+ protected
331
+
332
+ def paths(options)
333
+ options[:view].html_namespacing_rendered_paths
334
+ end
335
+ end
336
+
337
+ Other plugins are welcome; I will consider integrating them into this project.
338
+
339
+ == Tips and Tricks
340
+
341
+ You may find that HTML namespacing works best when each HTML partial is wrapped
342
+ in its own <tt>div</tt> tag. Both CSS's child selectors and jQuery's
343
+ <tt>find()</tt> function will ordinarily ignore the element with the namespace
344
+ class, only selecting sub-elements. For instance, with this HTML:
345
+
346
+ <div class="foos-show">
347
+ <p>Hello</p>
348
+ </div>
349
+
350
+ If you wrote the following CSS:
351
+
352
+ .foos-show div { font-weight: bold; }
353
+
354
+ Or this Sass:
355
+
356
+ div
357
+ :font-weight bold
358
+
359
+ Or the following JavaScript (following the <tt>$NS</tt> example above):
360
+
361
+ var $div = $NS().find('div');
362
+
363
+ Nothing will be selected, because the <tt>div</tt> element is not a child.
364
+ Instead, you need the following CSS:
365
+
366
+ div.foos-show { font-weight: bold; }
367
+
368
+ Or this sass:
369
+
370
+ div&
371
+ :font-weight bold
372
+
373
+ Or the following JavaScript:
374
+
375
+ var $div = $NS().filter('div');
376
+
377
+ Also, watch out for nesting: selectors with the <tt>foos-show</tt> namespace
378
+ will match anything inside any partials rendered by
379
+ <tt>foos/show.html.erb</tt>. As a rule of thumb to circumvent this problem: the
380
+ wider the namespaces's scope, the less CSS and JavaScript you should write
381
+ which depends on it. (Use sub-partials very liberally.)
382
+
383
+ HTML namespacing produces plenty of tiny CSS and/or JavaScript files. Best
384
+ practice is to bundle all namespaced files together: by virtue of being
385
+ namespaced, it is safe to concatenate them. (Advanced CSS users should
386
+ be thoughtful about
387
+ {cascading order and specificity}[http://www.w3.org/TR/CSS2/cascade.html#cascading-order]
388
+ and anybody bundling JavaScript files together should wrap each with
389
+ <tt>(function() { ... })();</tt> to prevent variable leakage.) CSS files can be
390
+ concatenated using a tool such as
391
+ {asset_library}[http://github.com/adamh/asset_library]; JavaScript files are
392
+ concatenated automatically in <tt>html_namespacing_javascript_tag()</tt>.
393
+
394
+ These and similar strategies should be considered when building an HTML
395
+ namespacing framework.
396
+
397
+ == License
398
+
399
+ I believe in software freedom, not any abomination thereof. This project is
400
+ released under the Public Domain, meaning I relinquish my copyright (to any
401
+ extend the law allows) and grant you all rights to use, modify, sell, or
402
+ otherwise take advantage of my software.
403
+
404
+ However, I do kindly request that, as a favor, you refrain from using my
405
+ software as part of an evil plan involving velociraptors and mind-controlling
406
+ robots (even though I would not be legally entitled to sue you for doing so).
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ name = 'html_namespacing_ext'
4
+
5
+ dir_config(name)
6
+ create_makefile(name)
@@ -0,0 +1,481 @@
1
+ #include <assert.h>
2
+ #include <errno.h>
3
+ #include <stdlib.h>
4
+ #include <string.h>
5
+
6
+ #include <stdio.h>
7
+
8
+ #include "html_namespacing.h"
9
+
10
+ #define WHITE_SPACE " \t\r\n"
11
+
12
+ static const struct IgnoreTag {
13
+ const char *str;
14
+ size_t len;
15
+ } IGNORE_TAGS[] = {
16
+ { "html", 4 },
17
+ { "head", 4 },
18
+ { "base", 4 },
19
+ { "meta", 4 },
20
+ { "title", 5 },
21
+ { "link", 4 },
22
+ { "script", 6 },
23
+ { "noscript", 8 },
24
+ { "style", 5 }
25
+ };
26
+ static const int NUM_IGNORE_TAGS = 9;
27
+
28
+ static int
29
+ determine_alloc_size_of_at_least(int i)
30
+ {
31
+ return i + 1024;
32
+ }
33
+
34
+ /*
35
+ * Ensures *r is long enough to hold len chars (using realloc).
36
+ *
37
+ * Arguments:
38
+ * - char **r: Pointer to the string (which need not be \0-terminated). Will
39
+ * be replaced by the new string.
40
+ * - size_t *r_len: Pointer to the length of the string. Will be replaced by
41
+ * the new length, which will be >= len.
42
+ * - size_t len: Minimum length of the new string.
43
+ *
44
+ * Returns:
45
+ * - 0 on success, whether or not *r changed length.
46
+ * - ENOMEM if reallocation failed. *r and *r_len will point to their old
47
+ * values, which will remain unchanged.
48
+ */
49
+ static int
50
+ ensure_string_length(
51
+ char **r,
52
+ size_t *r_len,
53
+ size_t len,
54
+ HtmlNamespacingAllocationStrategy allocation_strategy)
55
+ {
56
+ char *new_r;
57
+ size_t new_r_len;
58
+
59
+ new_r_len = *r_len;
60
+ if (new_r_len >= len) {
61
+ /* No need to realloc */
62
+ return 0;
63
+ }
64
+
65
+ while (new_r_len < len) {
66
+ new_r_len <<= 1;
67
+ }
68
+
69
+ new_r = allocation_strategy.realloc(*r, sizeof(char) * new_r_len);
70
+ if (!new_r) {
71
+ return ENOMEM;
72
+ }
73
+
74
+ *r = new_r;
75
+ *r_len = new_r_len;
76
+ return 0;
77
+ }
78
+
79
+ /*
80
+ * Tries to copy s into dest, possibly reallocating.
81
+ *
82
+ * Arguments:
83
+ * - dest: Beginning of destination string. May be reallocated during copy.
84
+ * - dest_len: Amount of memory allocated to dest. May be increased during
85
+ * copy.
86
+ * - dest_p: Pointer to end of destination string (potentially 1 past the
87
+ * allocated length of dest).
88
+ * - s: Source string.
89
+ *
90
+ * Returns:
91
+ * - 0 on success. dest may be changed; dest_p will be incremented.
92
+ * - ENOMEM if reallocation failed. dest and dest_p will remain unchanged.
93
+ */
94
+ static int
95
+ append_string(
96
+ char **dest,
97
+ size_t *dest_len,
98
+ char **dest_p,
99
+ const char *s,
100
+ size_t len,
101
+ HtmlNamespacingAllocationStrategy allocation_strategy)
102
+ {
103
+ int rv;
104
+ size_t dest_p_offset;
105
+ size_t new_dest_len;
106
+ char *new_dest;
107
+
108
+ new_dest = *dest;
109
+ dest_p_offset = *dest_p - *dest;
110
+ new_dest_len = dest_p_offset + len;
111
+
112
+ rv = ensure_string_length(
113
+ &new_dest, dest_len, new_dest_len, allocation_strategy);
114
+ if (rv == ENOMEM) {
115
+ return ENOMEM;
116
+ }
117
+ if (new_dest != *dest) {
118
+ *dest = new_dest;
119
+ *dest_p = new_dest + dest_p_offset;
120
+ }
121
+
122
+ strncpy(*dest_p, s, len);
123
+ *dest_p += len;
124
+
125
+ return 0;
126
+ };
127
+
128
+ enum {
129
+ PARSE_NORMAL,
130
+ PARSE_OPEN_TAG,
131
+ PARSE_OPEN_TAG_NAME,
132
+ PARSE_OPEN_TAG_ATTRIBUTE_NAME,
133
+ PARSE_OPEN_TAG_ATTRIBUTE_VALUE,
134
+ PARSE_OPEN_TAG_ATTRIBUTE_EQUALS,
135
+ PARSE_CLOSE_TAG,
136
+ PARSE_EMPTY_TAG, /* detected while we're inside PARSE_OPEN_TAG */
137
+ PARSE_COMMENT,
138
+ PARSE_XML_DECL,
139
+ PARSE_DOCTYPE,
140
+ PARSE_CDATA
141
+ };
142
+
143
+ static int
144
+ char_is_whitespace(char c)
145
+ {
146
+ return c == 0x20 || c == 0x09 || c == 0x0d || c == 0x0a;
147
+ }
148
+
149
+ static int
150
+ should_ignore_tag(const char *tag_name, size_t tag_len)
151
+ {
152
+ int i = 0;
153
+
154
+ for (i = 0; i < NUM_IGNORE_TAGS; i++) {
155
+ if (tag_len == IGNORE_TAGS[i].len
156
+ && 0 == strncmp(IGNORE_TAGS[i].str, tag_name, tag_len)) {
157
+ return 1;
158
+ }
159
+ }
160
+
161
+ return 0;
162
+ }
163
+
164
+ /*
165
+ * Adjusts the given HTML so that top-level elements get an added "class" of
166
+ * namespace.
167
+ *
168
+ * For instance, given
169
+ * "<div><span>Hello</span></div><p class="below">Goodbye</p>" and a
170
+ * namespace of "foo", returns
171
+ * "<div class="foo"><span>Hello</span></div><p class="below foo">Goodbye</p>".
172
+ *
173
+ * Arguments:
174
+ * - const char *html: Original HTML string. Must be valid HTML encoded as
175
+ * utf-8, \0-terminated.
176
+ * - int html_len: Length of HTML string, without the \0.
177
+ * - const char *namespace: Namespace to add. Must be of the form
178
+ * "[a-zA-Z][-_a-zA-Z0-9]*".
179
+ * - char **ret: Pointer to return value, which will be newly-allocated. Will
180
+ * be overwritten only on success.
181
+ * - int *ret_len: Pointer to return value's length. Will be overwritten only
182
+ * on success.
183
+ *
184
+ * Returns:
185
+ * - 0 on success. *ret will be overwritten to point to the newly-allocated
186
+ * string.
187
+ * - ENOMEM if *ret could not be allocated.
188
+ * - EINVAL if html is not valid XML.
189
+ */
190
+ int
191
+ add_namespace_to_html_with_length_and_allocation_strategy(
192
+ const char *html,
193
+ size_t html_len,
194
+ const char *ns,
195
+ char **ret,
196
+ size_t *ret_len,
197
+ HtmlNamespacingAllocationStrategy allocation_strategy)
198
+ {
199
+
200
+ #define APPEND_STRING(s, n) \
201
+ do { \
202
+ if (append_string(&r, &r_len, &r_p, s, n, allocation_strategy) != 0) goto error; \
203
+ } while (0)/*;*/
204
+ #define APPEND_END_OF_STRING() \
205
+ do { \
206
+ if (append_string(&r, &r_len, &r_p, "", 1, allocation_strategy) != 0) goto error; \
207
+ r_len -= 1; \
208
+ r_p -= 1; \
209
+ } while (0)/*;*/
210
+ #define COPY_TO_HERE() \
211
+ do { \
212
+ if (append_string(&r, &r_len, &r_p, html_first_uncopied_p, html_p - html_first_uncopied_p, allocation_strategy) != 0) goto error; \
213
+ html_first_uncopied_p = html_p; \
214
+ } while (0)/*;*/
215
+ #define ADVANCE(n) \
216
+ do { \
217
+ if (html_p + n <= html + html_len) { \
218
+ html_p += n; \
219
+ } else { \
220
+ html_p = html + html_len; \
221
+ } \
222
+ } while (0)/*;*/
223
+ #define ADVANCE_UNTIL_ONE_OF_THESE_CHARS(chars) \
224
+ do { \
225
+ const size_t num_chars = strcspn(html_p, chars); \
226
+ ADVANCE(num_chars); \
227
+ if (*html_p == '\0') \
228
+ goto end; \
229
+ } while (0) /*;*/
230
+
231
+ unsigned int state;
232
+ char *r; /* Start of retval */
233
+ char *r_p; /* Pointer in retval */
234
+ size_t r_len; /* Length of retval */
235
+ const char *html_first_uncopied_p;
236
+ const char *html_p;
237
+ const char *open_tag_name = NULL;
238
+ size_t open_tag_name_len = 0;
239
+ int num_tags_open;
240
+ int open_tag_attribute_is_class_attribute;
241
+ int open_tag_had_class_attribute;
242
+ const char *open_tag_attribute_value;
243
+ size_t ns_len = strlen(ns);
244
+
245
+ r_len = determine_alloc_size_of_at_least(html_len);
246
+ r = allocation_strategy.malloc(sizeof(char) * r_len);
247
+ if (!r) {
248
+ return ENOMEM;
249
+ }
250
+
251
+ html_first_uncopied_p = html_p = html;
252
+ state = PARSE_NORMAL;
253
+ r_p = r;
254
+ num_tags_open = 0;
255
+ open_tag_attribute_is_class_attribute = 0;
256
+ open_tag_had_class_attribute = 0;
257
+ open_tag_attribute_value = NULL;
258
+
259
+ while (1) {
260
+ if (html_p[0] == 0) break;
261
+
262
+ switch (state) {
263
+ case PARSE_NORMAL:
264
+ if (*html_p == '<') {
265
+ ADVANCE(1);
266
+ if (0 == strncmp("![CDATA[", html_p, 8)) {
267
+ state = PARSE_CDATA;
268
+ } else if (html_p[0] == '/') {
269
+ state = PARSE_CLOSE_TAG;
270
+ } else if (0 == strncmp("!--", html_p, 3)) {
271
+ state = PARSE_COMMENT;
272
+ } else if (0 == strncmp("!DOCTYPE", html_p, 8)) {
273
+ state = PARSE_DOCTYPE;
274
+ } else if (0 == strncmp("?xml", html_p, 4)) {
275
+ state = PARSE_XML_DECL;
276
+ } else {
277
+ open_tag_name = html_p;
278
+ state = PARSE_OPEN_TAG_NAME;
279
+ }
280
+ } else {
281
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("<");
282
+ }
283
+ break;
284
+ case PARSE_OPEN_TAG_NAME:
285
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS(WHITE_SPACE ">/");
286
+ open_tag_name_len = html_p - open_tag_name;
287
+ state = PARSE_OPEN_TAG;
288
+ break;
289
+ case PARSE_OPEN_TAG:
290
+ if (*html_p == '/' || *html_p == '>') {
291
+ if (num_tags_open == 0 && !open_tag_had_class_attribute
292
+ && !should_ignore_tag(open_tag_name, open_tag_name_len)) {
293
+ COPY_TO_HERE();
294
+ if (*html_p == '/' && char_is_whitespace(*(html_p - 1))) {
295
+ /* We're in an empty tag with a trailing space */
296
+ APPEND_STRING("class=\"", 7);
297
+ APPEND_STRING(ns, ns_len);
298
+ APPEND_STRING("\" ", 2);
299
+ } else {
300
+ APPEND_STRING(" class=\"", 8);
301
+ APPEND_STRING(ns, ns_len);
302
+ APPEND_STRING("\"", 1);
303
+ }
304
+ }
305
+
306
+ open_tag_had_class_attribute = 0;
307
+ open_tag_attribute_value = NULL;
308
+
309
+ if (*html_p == '/') {
310
+ state = PARSE_EMPTY_TAG;
311
+ } else {
312
+ num_tags_open++;
313
+ state = PARSE_NORMAL;
314
+ }
315
+ ADVANCE(1);
316
+ } else if (!char_is_whitespace(*html_p)) {
317
+ if (0 == strncmp(html_p, "class", 5)) {
318
+ open_tag_attribute_is_class_attribute = 1;
319
+ open_tag_had_class_attribute = 1;
320
+ } else {
321
+ open_tag_attribute_is_class_attribute = 0;
322
+ }
323
+ state = PARSE_OPEN_TAG_ATTRIBUTE_NAME;
324
+ } else {
325
+ ADVANCE(1);
326
+ }
327
+ break;
328
+ case PARSE_OPEN_TAG_ATTRIBUTE_NAME:
329
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("=");
330
+ ADVANCE(1);
331
+ state = PARSE_OPEN_TAG_ATTRIBUTE_EQUALS;
332
+ break;
333
+ case PARSE_OPEN_TAG_ATTRIBUTE_EQUALS:
334
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("'\"");
335
+ open_tag_attribute_value = html_p;
336
+ state = PARSE_OPEN_TAG_ATTRIBUTE_VALUE;
337
+ ADVANCE(1);
338
+ break;
339
+ case PARSE_OPEN_TAG_ATTRIBUTE_VALUE:
340
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("'\"");
341
+ /* open_tag_attribute_value is either ' or " */
342
+ while (*html_p != *open_tag_attribute_value) {
343
+ ADVANCE(1);
344
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("'\"");
345
+ }
346
+ if (open_tag_attribute_is_class_attribute
347
+ && num_tags_open == 0) {
348
+ COPY_TO_HERE();
349
+ APPEND_STRING(" ", 1);
350
+ APPEND_STRING(ns, ns_len);
351
+ }
352
+ open_tag_attribute_is_class_attribute = 0;
353
+ state = PARSE_OPEN_TAG;
354
+ ADVANCE(1);
355
+ break;
356
+ case PARSE_CLOSE_TAG:
357
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS(">");
358
+ num_tags_open--;
359
+ open_tag_attribute_value = NULL;
360
+ state = PARSE_NORMAL;
361
+ ADVANCE(1);
362
+ break;
363
+ case PARSE_EMPTY_TAG:
364
+ case PARSE_XML_DECL:
365
+ case PARSE_DOCTYPE:
366
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS(">");
367
+ ADVANCE(1);
368
+ state = PARSE_NORMAL;
369
+ break;
370
+ case PARSE_COMMENT:
371
+ ADVANCE(1); /* at least one */
372
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("-");
373
+ if (0 == strncmp("-->", html_p, 3)) {
374
+ ADVANCE(3);
375
+ state = PARSE_NORMAL;
376
+ }
377
+ /* else loop... */
378
+ break;
379
+ case PARSE_CDATA:
380
+ ADVANCE(1); /* at least one */
381
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("]");
382
+ if (0 == strncmp("]]>", html_p, 3)) {
383
+ ADVANCE(3);
384
+ state = PARSE_NORMAL;
385
+ }
386
+ /* else loop... */
387
+ break;
388
+ default:
389
+ assert(0);
390
+ }
391
+ }
392
+
393
+ end:
394
+ COPY_TO_HERE();
395
+ APPEND_END_OF_STRING();
396
+
397
+ if (state != PARSE_NORMAL
398
+ || (r_p - r) < html_len
399
+ || html_p != html + html_len
400
+ || num_tags_open != 0
401
+ || open_tag_attribute_is_class_attribute
402
+ || open_tag_attribute_value)
403
+ {
404
+ allocation_strategy.free (r);
405
+ *ret = NULL;
406
+ *ret_len = -1;
407
+ return EINVAL;
408
+ }
409
+
410
+ *ret = r;
411
+ *ret_len = r_p - r;
412
+ return 0;
413
+
414
+ error:
415
+ allocation_strategy.free(r);
416
+ *ret = NULL;
417
+ *ret_len = -1;
418
+ return ENOMEM;
419
+ }
420
+
421
+ int
422
+ add_namespace_to_html_with_length(
423
+ const char *html,
424
+ size_t html_len,
425
+ const char *ns,
426
+ char **ret,
427
+ size_t *ret_len)
428
+ {
429
+ HtmlNamespacingAllocationStrategy allocation_strategy;
430
+ allocation_strategy.malloc = malloc;
431
+ allocation_strategy.free = free;
432
+ allocation_strategy.realloc = realloc;
433
+
434
+ return add_namespace_to_html_with_length_and_allocation_strategy(
435
+ html, html_len, ns, ret, ret_len, allocation_strategy);
436
+ }
437
+
438
+
439
+ #if INCLUDE_MAIN
440
+ #include <stdio.h>
441
+
442
+ int
443
+ main(int argc, char **argv)
444
+ {
445
+ const char *html;
446
+ size_t html_len;
447
+ const char *ns;
448
+ char *ret;
449
+ size_t ret_len;
450
+ int rv;
451
+ HtmlNamespacingAllocationStrategy allocation_strategy;
452
+
453
+ if (argc != 3) {
454
+ printf("usage: %s HTML_STRING NAMESPACE", argv[0]);
455
+ return 1;
456
+ }
457
+
458
+ html = argv[1];
459
+ html_len = strlen(html);
460
+ ns = argv[2];
461
+
462
+ printf("html: %s\n", html);
463
+ printf("html_len: %d\n", html_len);
464
+ printf("ns: %s\n", ns);
465
+
466
+ allocation_strategy.malloc = malloc;
467
+ allocation_strategy.free = free;
468
+ allocation_strategy.realloc = realloc;
469
+
470
+ rv = add_namespace_to_html_with_length_and_allocation_strategy(html, html_len, ns, &ret, &ret_len, allocation_strategy);
471
+
472
+ if (ret) {
473
+ printf("ret: %s\n", ret);
474
+ printf("ret_len: %d\n", ret_len);
475
+ } else {
476
+ printf("Failed!\n");
477
+ }
478
+
479
+ return rv;
480
+ }
481
+ #endif /* INCLUDE_MAIN */