patch-html_namespacing 0.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +406 -0
- data/ext/html_namespacing/extconf.rb +6 -0
- data/ext/html_namespacing/html_namespacing.c +481 -0
- data/ext/html_namespacing/html_namespacing.h +35 -0
- data/ext/html_namespacing/html_namespacing_ext.c +97 -0
- data/lib/html_namespacing.rb +9 -0
- data/lib/html_namespacing/plugin.rb +10 -0
- data/lib/html_namespacing/plugin/dom_scan_jquery.js +44 -0
- data/lib/html_namespacing/plugin/dom_scan_jquery.js.compressed +1 -0
- data/lib/html_namespacing/plugin/rails.rb +206 -0
- data/lib/html_namespacing/plugin/sass.rb +93 -0
- data/spec/c_extension_spec.rb +58 -0
- data/spec/spec_helper.rb +4 -0
- metadata +87 -0
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,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 */
|