patch-html_namespacing 0.2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 */