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.
- 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 */
|