html_namespacing 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -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,482 @@
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 char* const IGNORE_TAGS[] = {
13
+ "html",
14
+ "head",
15
+ "base",
16
+ "meta",
17
+ "title",
18
+ "link",
19
+ "script",
20
+ "noscript",
21
+ "style",
22
+ NULL
23
+ };
24
+
25
+ static int
26
+ determine_alloc_size_of_at_least(int i)
27
+ {
28
+ return i + 1024;
29
+ }
30
+
31
+ /*
32
+ * Ensures *r is long enough to hold len chars (using realloc).
33
+ *
34
+ * Arguments:
35
+ * - char **r: Pointer to the string (which need not be \0-terminated). Will
36
+ * be replaced by the new string.
37
+ * - size_t *r_len: Pointer to the length of the string. Will be replaced by
38
+ * the new length, which will be >= len.
39
+ * - size_t len: Minimum length of the new string.
40
+ *
41
+ * Returns:
42
+ * - 0 on success, whether or not *r changed length.
43
+ * - ENOMEM if reallocation failed. *r and *r_len will point to their old
44
+ * values, which will remain unchanged.
45
+ */
46
+ static int
47
+ ensure_string_length(
48
+ char **r,
49
+ size_t *r_len,
50
+ size_t len,
51
+ HtmlNamespacingAllocationStrategy allocation_strategy)
52
+ {
53
+ char *new_r;
54
+ size_t new_r_len;
55
+
56
+ new_r_len = *r_len;
57
+ if (new_r_len >= len) {
58
+ /* No need to realloc */
59
+ return 0;
60
+ }
61
+
62
+ while (new_r_len < len) {
63
+ new_r_len <<= 1;
64
+ }
65
+
66
+ new_r = allocation_strategy.realloc(*r, sizeof(char) * new_r_len);
67
+ if (!new_r) {
68
+ return ENOMEM;
69
+ }
70
+
71
+ *r = new_r;
72
+ *r_len = new_r_len;
73
+ return 0;
74
+ }
75
+
76
+ /*
77
+ * Tries to copy s into dest, possibly reallocating.
78
+ *
79
+ * Arguments:
80
+ * - dest: Beginning of destination string. May be reallocated during copy.
81
+ * - dest_len: Amount of memory allocated to dest. May be increased during
82
+ * copy.
83
+ * - dest_p: Pointer to end of destination string (potentially 1 past the
84
+ * allocated length of dest).
85
+ * - s: Source string.
86
+ *
87
+ * Returns:
88
+ * - 0 on success. dest may be changed; dest_p will be incremented.
89
+ * - ENOMEM if reallocation failed. dest and dest_p will remain unchanged.
90
+ */
91
+ static int
92
+ append_string(
93
+ char **dest,
94
+ size_t *dest_len,
95
+ char **dest_p,
96
+ const char *s,
97
+ size_t len,
98
+ HtmlNamespacingAllocationStrategy allocation_strategy)
99
+ {
100
+ int rv;
101
+ size_t dest_p_offset;
102
+ size_t new_dest_len;
103
+ char *new_dest;
104
+
105
+ new_dest = *dest;
106
+ dest_p_offset = *dest_p - *dest;
107
+ new_dest_len = dest_p_offset + len;
108
+
109
+ rv = ensure_string_length(
110
+ &new_dest, dest_len, new_dest_len, allocation_strategy);
111
+ if (rv == ENOMEM) {
112
+ return ENOMEM;
113
+ }
114
+ if (new_dest != *dest) {
115
+ *dest = new_dest;
116
+ *dest_p = new_dest + dest_p_offset;
117
+ }
118
+
119
+ strncpy(*dest_p, s, len);
120
+ *dest_p += len;
121
+
122
+ return 0;
123
+ };
124
+
125
+ enum {
126
+ PARSE_NORMAL,
127
+ PARSE_OPEN_TAG,
128
+ PARSE_OPEN_TAG_NAME,
129
+ PARSE_OPEN_TAG_ATTRIBUTE_NAME,
130
+ PARSE_OPEN_TAG_ATTRIBUTE_VALUE,
131
+ PARSE_OPEN_TAG_ATTRIBUTE_EQUALS,
132
+ PARSE_CLOSE_TAG,
133
+ PARSE_EMPTY_TAG, /* detected while we're inside PARSE_OPEN_TAG */
134
+ PARSE_COMMENT,
135
+ PARSE_XML_DECL,
136
+ PARSE_DOCTYPE,
137
+ PARSE_CDATA
138
+ };
139
+
140
+ static int
141
+ char_is_whitespace(char c)
142
+ {
143
+ return c == 0x20 || c == 0x09 || c == 0x0d || c == 0x0a;
144
+ }
145
+
146
+ static int
147
+ should_ignore_tag(const char *tag_name, size_t tag_len)
148
+ {
149
+ int i = 0;
150
+ const char *test_ignore;
151
+
152
+ for (i = 0; (test_ignore = IGNORE_TAGS[i]); i++) {
153
+ if (0 == strncmp(test_ignore, tag_name, tag_len)
154
+ && strlen(test_ignore) == tag_len)
155
+ {
156
+ return 1;
157
+ }
158
+ }
159
+
160
+ return 0;
161
+ }
162
+
163
+ /*
164
+ * Adjusts the given HTML so that top-level elements get an added "class" of
165
+ * namespace.
166
+ *
167
+ * For instance, given
168
+ * "<div><span>Hello</span></div><p class="below">Goodbye</p>" and a
169
+ * namespace of "foo", returns
170
+ * "<div class="foo"><span>Hello</span></div><p class="below foo">Goodbye</p>".
171
+ *
172
+ * Arguments:
173
+ * - const char *html: Original HTML string. Must be valid HTML, encoded as
174
+ * utf-8.
175
+ * - int html_len: Length of HTML string.
176
+ * - const char *namespace: Namespace to add. Must be of the form
177
+ * "[a-zA-Z][-_a-zA-Z0-9]*".
178
+ * - char **ret: Pointer to return value, which will be newly-allocated. Will
179
+ * be overwritten only on success.
180
+ * - int *ret_len: Pointer to return value's length. Will be overwritten only
181
+ * on success.
182
+ *
183
+ * Returns:
184
+ * - 0 on success. *ret will be overwritten to point to the newly-allocated
185
+ * string.
186
+ * - ENOMEM if *ret could not be allocated.
187
+ * - EINVAL if html is not valid XML.
188
+ */
189
+ int
190
+ add_namespace_to_html_with_length_and_allocation_strategy(
191
+ const char *html,
192
+ size_t html_len,
193
+ const char *ns,
194
+ char **ret,
195
+ size_t *ret_len,
196
+ HtmlNamespacingAllocationStrategy allocation_strategy)
197
+ {
198
+
199
+ #define APPEND_STRING(s) \
200
+ do { \
201
+ if (append_string(&r, &r_len, &r_p, s, strlen(s), allocation_strategy) != 0) goto error; \
202
+ } while (0)/*;*/
203
+ #define APPEND_END_OF_STRING() \
204
+ do { \
205
+ if (append_string(&r, &r_len, &r_p, "", 1, allocation_strategy) != 0) goto error; \
206
+ r_len -= 1; \
207
+ r_p -= 1; \
208
+ } while (0)/*;*/
209
+ #define COPY_TO_HERE() \
210
+ do { \
211
+ if (append_string(&r, &r_len, &r_p, html_first_uncopied_p, html_p - html_first_uncopied_p, allocation_strategy) != 0) goto error; \
212
+ html_first_uncopied_p = html_p; \
213
+ } while (0)/*;*/
214
+ #define ADVANCE(n) \
215
+ do { \
216
+ if (html_p + n <= html + html_len) { \
217
+ html_p += n; \
218
+ } else { \
219
+ html_p = html + html_len; \
220
+ } \
221
+ } while (0)/*;*/
222
+ #define ADVANCE_UNTIL_ONE_OF_THESE_CHARS(chars) \
223
+ ADVANCE(strcspn(html_p, chars))/*;*/
224
+
225
+ unsigned int state;
226
+ char *r; /* Start of retval */
227
+ char *r_p; /* Pointer in retval */
228
+ size_t r_len; /* Length of retval */
229
+ const char *html_first_uncopied_p;
230
+ const char *html_p;
231
+ const char *open_tag_name = NULL;
232
+ size_t open_tag_name_len = 0;
233
+ size_t num_chars_remaining;
234
+ int num_tags_open;
235
+ int open_tag_attribute_is_class_attribute;
236
+ int open_tag_had_class_attribute;
237
+ const char *open_tag_attribute_value;
238
+
239
+ r_len = determine_alloc_size_of_at_least(html_len);
240
+ r = allocation_strategy.malloc(sizeof(char) * r_len);
241
+ if (!r) {
242
+ return ENOMEM;
243
+ }
244
+
245
+ html_first_uncopied_p = html_p = html;
246
+ state = PARSE_NORMAL;
247
+ r_p = r;
248
+ num_tags_open = 0;
249
+ open_tag_attribute_is_class_attribute = 0;
250
+ open_tag_had_class_attribute = 0;
251
+ open_tag_attribute_value = NULL;
252
+
253
+ while (1) {
254
+ num_chars_remaining = html_len - (html_p - html);
255
+ if (num_chars_remaining <= 0) break;
256
+
257
+ switch (state) {
258
+ case PARSE_NORMAL:
259
+ if (*html_p == '<') {
260
+ ADVANCE(1);
261
+ if (num_chars_remaining >= 9
262
+ && 0 == strncmp("![CDATA[", html_p, 8)) {
263
+ state = PARSE_CDATA;
264
+ } else if (num_chars_remaining >= 2 && html_p[0] == '/') {
265
+ state = PARSE_CLOSE_TAG;
266
+ } else if (num_chars_remaining >= 4
267
+ && 0 == strncmp("!--", html_p, 3)) {
268
+ state = PARSE_COMMENT;
269
+ } else if (num_chars_remaining >= 9
270
+ && 0 == strncmp("!DOCTYPE", html_p, 8)) {
271
+ state = PARSE_DOCTYPE;
272
+ } else if (num_chars_remaining >= 5
273
+ && 0 == strncmp("?xml", html_p, 4)) {
274
+ state = PARSE_XML_DECL;
275
+ } else {
276
+ open_tag_name = html_p;
277
+ state = PARSE_OPEN_TAG_NAME;
278
+ }
279
+ } else {
280
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("<");
281
+ }
282
+ break;
283
+ case PARSE_OPEN_TAG_NAME:
284
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS(WHITE_SPACE ">/");
285
+ open_tag_name_len = html_p - open_tag_name;
286
+ state = PARSE_OPEN_TAG;
287
+ break;
288
+ case PARSE_OPEN_TAG:
289
+ if (*html_p == '/' || *html_p == '>') {
290
+ if (num_tags_open == 0 && !open_tag_had_class_attribute
291
+ && !should_ignore_tag(open_tag_name, open_tag_name_len)) {
292
+ COPY_TO_HERE();
293
+ if (*html_p == '/' && char_is_whitespace(*(html_p - 1))) {
294
+ /* We're in an empty tag with a trailing space */
295
+ APPEND_STRING("class=\"");
296
+ APPEND_STRING(ns);
297
+ APPEND_STRING("\" ");
298
+ } else {
299
+ APPEND_STRING(" class=\"");
300
+ APPEND_STRING(ns);
301
+ APPEND_STRING("\"");
302
+ }
303
+ }
304
+
305
+ open_tag_had_class_attribute = 0;
306
+ open_tag_attribute_value = NULL;
307
+
308
+ if (*html_p == '/') {
309
+ state = PARSE_EMPTY_TAG;
310
+ } else {
311
+ num_tags_open++;
312
+ state = PARSE_NORMAL;
313
+ }
314
+ ADVANCE(1);
315
+ } else if (!char_is_whitespace(*html_p)) {
316
+ if (num_chars_remaining >= 5
317
+ && 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(" ");
350
+ APPEND_STRING(ns);
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 (num_chars_remaining >= 3
374
+ && 0 == strncmp("-->", html_p, 3)) {
375
+ ADVANCE(3);
376
+ state = PARSE_NORMAL;
377
+ }
378
+ /* else loop... */
379
+ break;
380
+ case PARSE_CDATA:
381
+ ADVANCE(1); /* at least one */
382
+ ADVANCE_UNTIL_ONE_OF_THESE_CHARS("]");
383
+ if (num_chars_remaining >= 3
384
+ && 0 == strncmp("]]>", html_p, 3)) {
385
+ ADVANCE(3);
386
+ state = PARSE_NORMAL;
387
+ }
388
+ /* else loop... */
389
+ break;
390
+ default:
391
+ assert(0);
392
+ }
393
+ }
394
+
395
+ COPY_TO_HERE();
396
+ APPEND_END_OF_STRING();
397
+
398
+ if (state != PARSE_NORMAL
399
+ || (r_p - r) < html_len
400
+ || num_chars_remaining != 0
401
+ || num_tags_open != 0
402
+ || open_tag_attribute_is_class_attribute
403
+ || open_tag_attribute_value)
404
+ {
405
+ allocation_strategy.free (r);
406
+ *ret = NULL;
407
+ *ret_len = -1;
408
+ return EINVAL;
409
+ }
410
+
411
+ *ret = r;
412
+ *ret_len = r_p - r;
413
+ return 0;
414
+
415
+ error:
416
+ allocation_strategy.free(r);
417
+ *ret = NULL;
418
+ *ret_len = -1;
419
+ return ENOMEM;
420
+ }
421
+
422
+ int
423
+ add_namespace_to_html_with_length(
424
+ const char *html,
425
+ size_t html_len,
426
+ const char *ns,
427
+ char **ret,
428
+ size_t *ret_len)
429
+ {
430
+ HtmlNamespacingAllocationStrategy allocation_strategy;
431
+ allocation_strategy.malloc = malloc;
432
+ allocation_strategy.free = free;
433
+ allocation_strategy.realloc = realloc;
434
+
435
+ return add_namespace_to_html_with_length_and_allocation_strategy(
436
+ html, html_len, ns, ret, ret_len, allocation_strategy);
437
+ }
438
+
439
+
440
+ #if INCLUDE_MAIN
441
+ #include <stdio.h>
442
+
443
+ int
444
+ main(int argc, char **argv)
445
+ {
446
+ const char *html;
447
+ size_t html_len;
448
+ const char *ns;
449
+ char *ret;
450
+ size_t ret_len;
451
+ int rv;
452
+ HtmlNamespacingAllocationStrategy allocation_strategy;
453
+
454
+ if (argc != 3) {
455
+ printf("usage: %s HTML_STRING NAMESPACE", argv[0]);
456
+ return 1;
457
+ }
458
+
459
+ html = argv[1];
460
+ html_len = strlen(html);
461
+ ns = argv[2];
462
+
463
+ printf("html: %s\n", html);
464
+ printf("html_len: %d\n", html_len);
465
+ printf("ns: %s\n", ns);
466
+
467
+ allocation_strategy.malloc = malloc;
468
+ allocation_strategy.free = free;
469
+ allocation_strategy.realloc = realloc;
470
+
471
+ rv = add_namespace_to_html_with_length_and_allocation_strategy(html, html_len, ns, &ret, &ret_len, allocation_strategy);
472
+
473
+ if (ret) {
474
+ printf("ret: %s\n", ret);
475
+ printf("ret_len: %d\n", ret_len);
476
+ } else {
477
+ printf("Failed!\n");
478
+ }
479
+
480
+ return rv;
481
+ }
482
+ #endif /* INCLUDE_MAIN */