adamh-html_namespacing 0.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/Manifest +12 -0
- data/README.rdoc +274 -0
- data/Rakefile +12 -0
- data/ext/html_namespacing/extconf.rb +6 -0
- data/ext/html_namespacing/html_namespacing.c +531 -0
- data/ext/html_namespacing/html_namespacing.h +35 -0
- data/ext/html_namespacing/html_namespacing_ext.c +97 -0
- data/html_namespacing.gemspec +36 -0
- data/lib/html_namespacing.rb +6 -0
- data/lib/html_namespacing/plugin/rails.rb +42 -0
- data/test/c_extension_test.rb +52 -0
- data/test/test_helper.rb +3 -0
- metadata +80 -0
data/Manifest
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
README.rdoc
|
2
|
+
Rakefile
|
3
|
+
test/c_extension_test.rb
|
4
|
+
test/test_helper.rb
|
5
|
+
ext/html_namespacing/extconf.rb
|
6
|
+
ext/html_namespacing/html_namespacing.h
|
7
|
+
ext/html_namespacing/html_namespacing.c
|
8
|
+
ext/html_namespacing/html_namespacing_ext.c
|
9
|
+
lib/html_namespacing.rb
|
10
|
+
lib/html_namespacing/plugin/rails.rb
|
11
|
+
Manifest
|
12
|
+
html_namespacing.gemspec
|
data/README.rdoc
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
= HTML Namespacing
|
2
|
+
|
3
|
+
HTML Namespacing automatically adds HTML class attributes to partial HTML
|
4
|
+
code.
|
5
|
+
|
6
|
+
== Installing
|
7
|
+
|
8
|
+
gem install adamh-html_namespacing --source http://gems.github.com
|
9
|
+
|
10
|
+
== Using
|
11
|
+
|
12
|
+
HTML Namespacing can be used on its own for a snippet of code:
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
require 'html_namespacing'
|
16
|
+
|
17
|
+
html = '<p>Here is some <em>HTML</em>!</p>'
|
18
|
+
|
19
|
+
# Returns '<p class="foo">Here is some <em>HTML</em>!</p>'
|
20
|
+
namespaced = HtmlNamespacing::add_namespace_to_html(html, 'foo')
|
21
|
+
|
22
|
+
== Details
|
23
|
+
|
24
|
+
HTML namespacing expects valid, properly-nested HTML (not XML). It has no
|
25
|
+
effect on doctype tags, XML declaration, comments, and the HTML tags which do
|
26
|
+
not support the <tt>class</tt> attribute: <tt>html</tt>, <tt>head</tt>,
|
27
|
+
<tt>base</tt>, <tt>meta</tt>, <tt>title</tt>, <tt>link</tt>, <tt>script</tt>,
|
28
|
+
<tt>noscript</tt>, <tt>style</tt>.
|
29
|
+
|
30
|
+
Only the root tags of the partial HTML will be namespaced. For instance, all
|
31
|
+
<tt>p</tt> tags in this example will have a class added, but no other tags:
|
32
|
+
|
33
|
+
<p>This is a root tag. <b>This tag is nested.</b></p>
|
34
|
+
<p>This is another root tag.</p>
|
35
|
+
|
36
|
+
Remember, <tt>html</tt> (and other HTML tags which do not allow the
|
37
|
+
<tt>class</tt> attribute) will be ignored. This HTML will pass through
|
38
|
+
unchanged:
|
39
|
+
|
40
|
+
<?xml version="1.0"?>
|
41
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
42
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
43
|
+
<!-- Here is a comment -->
|
44
|
+
<html>
|
45
|
+
<head>
|
46
|
+
<title>Hello</title>
|
47
|
+
</head>
|
48
|
+
<body>
|
49
|
+
<p>Blah blah blah</p>
|
50
|
+
</body>
|
51
|
+
</html>
|
52
|
+
|
53
|
+
Though the actual namespacing functions are written in pure C with no
|
54
|
+
dependencies, only Ruby bindings are available at this time.
|
55
|
+
|
56
|
+
== Integrating in your Project
|
57
|
+
|
58
|
+
More likely, you want to integrate HTML namespacing into a framework. Here is
|
59
|
+
a Rails example:
|
60
|
+
|
61
|
+
In <tt>config/environment.rb</tt>:
|
62
|
+
|
63
|
+
config.gem 'html_namespacing'
|
64
|
+
|
65
|
+
In <tt>config/initializers/html_namespacing.rb</tt>:
|
66
|
+
|
67
|
+
HtmlNamespacing::Plugin::Rails.install
|
68
|
+
|
69
|
+
Now, all templates will have HTML namespacing applied. For instance, with a
|
70
|
+
<tt>views/foos/show.html.erb</tt> like this:
|
71
|
+
|
72
|
+
<p>
|
73
|
+
<b>Name:</b>
|
74
|
+
<%=h @foo.name %>
|
75
|
+
</p>
|
76
|
+
|
77
|
+
<%= render(:partial => 'details', :locals => { :foo => @foo }) %>
|
78
|
+
|
79
|
+
<%= link_to 'Edit', foo_edit_path(@foo) %> |
|
80
|
+
<%= link_to 'Back', foos_path %>
|
81
|
+
|
82
|
+
The following HTML might be generated (depending on the <tt>_details</tt>
|
83
|
+
partial and the data in the actual <tt>Foo</tt> object):
|
84
|
+
|
85
|
+
<p class="foos-show">
|
86
|
+
<b>Name:</b>
|
87
|
+
Foo
|
88
|
+
</p>
|
89
|
+
|
90
|
+
<p class="foos-_details foos-show">
|
91
|
+
<b>Description:</b>
|
92
|
+
Bar
|
93
|
+
</p>
|
94
|
+
|
95
|
+
<a href="/foos/1/edit" class="hellos-show">Edit</a> |
|
96
|
+
<a href="/foos" class="hellos-show">Back</a>
|
97
|
+
|
98
|
+
Rails will integrate with all HTML templates, regardless of their
|
99
|
+
implementation; feel free to use Haml.
|
100
|
+
|
101
|
+
=== Options
|
102
|
+
|
103
|
+
Available options to <tt>install</tt> are:
|
104
|
+
|
105
|
+
<tt>:template_to_namespace_callback</tt>: Ruby lambda function which accepts an
|
106
|
+
<tt>ActionView::Template</tt> and returns an HTML namespacing string. The
|
107
|
+
default is:
|
108
|
+
|
109
|
+
lambda { |template| template.path =~ /^([^\.]+)/ && $1.gsub('/', '-') || nil }
|
110
|
+
|
111
|
+
If the callback returns <tt>nil</tt>, HTML namespacing will not be applied.
|
112
|
+
|
113
|
+
== Why?
|
114
|
+
|
115
|
+
HTML namespacing gives huge benefits when writing CSS and JavaScript:
|
116
|
+
especially if the CSS and JavaScript components are automatically namespaced
|
117
|
+
in a similar manner.
|
118
|
+
|
119
|
+
Imagine we have the following namespaced HTML:
|
120
|
+
|
121
|
+
<div class="foos-show">
|
122
|
+
<p>In the show partial</p>
|
123
|
+
<div class="foos-_details">
|
124
|
+
<p>In the details partial</p>
|
125
|
+
<div class="foos-_description">
|
126
|
+
<p>In the description</p>
|
127
|
+
</div>
|
128
|
+
</div>
|
129
|
+
</div>
|
130
|
+
|
131
|
+
=== CSS Namespacing
|
132
|
+
|
133
|
+
We can set three CSS rules:
|
134
|
+
|
135
|
+
.foos-show p { font-size: 1.1em; }
|
136
|
+
.foos-_details p { font-weight: bold; }
|
137
|
+
.foos-_description p { font-style: italic; }
|
138
|
+
|
139
|
+
In such an example, the top paragraph would be large, the second would be large
|
140
|
+
and bold, and the third would be large, bold, and italic.
|
141
|
+
|
142
|
+
The benefit comes when automating these namespaces. For instance, if the CSS
|
143
|
+
rules were placed in <tt>stylesheets/views/foos/show.css</tt>,
|
144
|
+
<tt>stylesheets/views/foos/_details.css</tt>, and
|
145
|
+
<tt>stylesheets/views/foos/_description.css</tt>, respectively, and a
|
146
|
+
preprocessor inserted the namespace before (or within) every rule in the file,
|
147
|
+
all CSS namespacing would be done for free, so you would be able to write:
|
148
|
+
|
149
|
+
<tt>stylesheets/views/foos/show.css</tt>:
|
150
|
+
|
151
|
+
p { font-size: 1.1em; }
|
152
|
+
|
153
|
+
<tt>stylesheets/views/foos/_details.css</tt>:
|
154
|
+
|
155
|
+
p { font-weight: bold; }
|
156
|
+
|
157
|
+
<tt>stylesheets/views/foos/_description.css</tt>:
|
158
|
+
|
159
|
+
p { font-style: italic; }
|
160
|
+
|
161
|
+
Thus, the namespaces would never need to be explicitly mentioned. The
|
162
|
+
framework used to generate such CSS is left (for now) as an exercise to the
|
163
|
+
reader.
|
164
|
+
|
165
|
+
=== jQuery Namespacing
|
166
|
+
|
167
|
+
Insert the following before all other JavaScript:
|
168
|
+
|
169
|
+
$(document).ready(function() {
|
170
|
+
$.NS = {};
|
171
|
+
|
172
|
+
var SPLIT_REGEX = /\s+/;
|
173
|
+
|
174
|
+
function populate_NS_from_node(node) {
|
175
|
+
var node_stack = [];
|
176
|
+
node_stack.push(node);
|
177
|
+
|
178
|
+
while (node_stack.length) {
|
179
|
+
var cur_node = node_stack.pop();
|
180
|
+
|
181
|
+
// Populate entry in $.NS
|
182
|
+
var class_name_list = node.className;
|
183
|
+
var class_names = class_name_list.split(SPLIT_REGEX);
|
184
|
+
for (var i = 0; i < class_names.length; i++) {
|
185
|
+
var class_name = class_names[i];
|
186
|
+
if (!$.NS[class_name]) {
|
187
|
+
$.NS[class_name] = [cur_node];
|
188
|
+
} else {
|
189
|
+
$.NS[class_name].push(cur_node);
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
// "recurse" to the children, so they are handled in document order
|
194
|
+
var children = cur_node.childNodes;
|
195
|
+
for (var j = children.length - 1; j >= 0; j--) {
|
196
|
+
var subnode = children[j];
|
197
|
+
node_stack.push(subnode);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
populate_NS_from_node(document.getElementsByTagName('body')[0]);
|
203
|
+
});
|
204
|
+
|
205
|
+
(The CPU cost of this code is small but nonzero, as it precomputes many
|
206
|
+
selectors. On a 75kb page with a 2.5Ghz computer, it might take 75ms with
|
207
|
+
Firebug profiling turned on. This may or may not be faster than writing each
|
208
|
+
selector by hand.)
|
209
|
+
|
210
|
+
Afterwards, if you set up a JavaScript framework similar to the CSS framework
|
211
|
+
above, you can easily namespace JavaScript behavior:
|
212
|
+
|
213
|
+
<tt>javascripts/onload/foos/_description.js</tt>:
|
214
|
+
|
215
|
+
$(document).ready(function() {
|
216
|
+
var $NS = $.NS['foos-_description']; // auto-generated line at top of file
|
217
|
+
$NS.find('p').click(function() { alert("Yup, you clicked!"); });
|
218
|
+
});
|
219
|
+
|
220
|
+
In the above file, if every line but the third is automatically generated based
|
221
|
+
on the file's location (an exercise, for now, left to the reader), the third
|
222
|
+
line can be written in full confidence that only paragraphs within the
|
223
|
+
<tt>_description</tt> partial will have this jQuery code bound. The end result?
|
224
|
+
You can write code specific to one Rails partial without actually writing the
|
225
|
+
partial's name anywhere, and you will not need to rewrite any selectors if you
|
226
|
+
rename or move the partial.
|
227
|
+
|
228
|
+
== Tips and Tricks
|
229
|
+
|
230
|
+
You may find that HTML namespacing works best when each HTML partial is wrapped
|
231
|
+
in its own <tt>div</tt> tag. Both CSS's child selectors and jQuery's <tt>find()</tt> function
|
232
|
+
will ordinarily ignore the element with the namespace class, only selecting
|
233
|
+
sub-elements. For instance, with this HTML:
|
234
|
+
|
235
|
+
<div class="foos-show">
|
236
|
+
<p>Hello</p>
|
237
|
+
</div>
|
238
|
+
|
239
|
+
If you wrote the following CSS:
|
240
|
+
|
241
|
+
.foos-show div { font-weight: bold; }
|
242
|
+
|
243
|
+
Or the following JavaScript (following the <tt>$NS</tt> example above):
|
244
|
+
|
245
|
+
var $div = $NS.find('div');
|
246
|
+
|
247
|
+
Nothing will be selected, because the <tt>div</tt> element is not a child. You
|
248
|
+
would need the following CSS:
|
249
|
+
|
250
|
+
div.foos-show { font-weight: bold; }
|
251
|
+
|
252
|
+
Or the following JavaScript:
|
253
|
+
|
254
|
+
var $div = $NS.filter('div');
|
255
|
+
|
256
|
+
Also, watch out for nesting: selectors with the <tt>foos-show</tt> namespace
|
257
|
+
will match anything inside any partials rendered by
|
258
|
+
<tt>foos/show.html.erb</tt>. As a rule of thumb to circumvent this problem: the
|
259
|
+
wider the namespaces's scope, the less CSS and JavaScript you should write
|
260
|
+
which depends on it. (Rule of thumb: use sub-partials very liberally.)
|
261
|
+
|
262
|
+
These and similar strategies should be considered when building your automated
|
263
|
+
HTML namespacing framework.
|
264
|
+
|
265
|
+
== License
|
266
|
+
|
267
|
+
I believe in software freedom, not any abomination thereof. This project is
|
268
|
+
released under the Public Domain, meaning I relinquish my copyright (to any
|
269
|
+
extend the law allows) and grant you all rights to use, modify, sell, or
|
270
|
+
otherwise take advantage of my software.
|
271
|
+
|
272
|
+
However, I do kindly request that, as a favor, you refrain from using my
|
273
|
+
software as part of an evil plan involving velociraptors and mind-controlling
|
274
|
+
robots (even though I would not be legally entitled to sue you for doing so).
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('html_namespacing', '0.0.1') do |p|
|
6
|
+
p.author = 'Adam Hooper'
|
7
|
+
p.email = 'adam@adamhooper.com'
|
8
|
+
p.summary = 'Automatic HTML namespacing'
|
9
|
+
p.description = 'Inserts "class=" attributes within snippets of HTML so CSS and JavaScript can use automatic scopes'
|
10
|
+
p.url = 'http://github.com/adamh/html_namespacing'
|
11
|
+
p.rdoc_files = ['README.rdoc'] # echoe seems to gather everything twice
|
12
|
+
end
|
@@ -0,0 +1,531 @@
|
|
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
|
+
static const char* const IGNORE_TAGS[] = {
|
11
|
+
"html",
|
12
|
+
"head",
|
13
|
+
"base",
|
14
|
+
"meta",
|
15
|
+
"title",
|
16
|
+
"link",
|
17
|
+
"script",
|
18
|
+
"noscript",
|
19
|
+
"style",
|
20
|
+
NULL
|
21
|
+
};
|
22
|
+
|
23
|
+
/*
|
24
|
+
* Returns the first power of 2 which is >= the given length.
|
25
|
+
*/
|
26
|
+
static int
|
27
|
+
round_to_power_of_two(int i)
|
28
|
+
{
|
29
|
+
int j = 1;
|
30
|
+
|
31
|
+
while (j < i) {
|
32
|
+
j <<= 1;
|
33
|
+
}
|
34
|
+
|
35
|
+
return j;
|
36
|
+
}
|
37
|
+
|
38
|
+
/*
|
39
|
+
* Returns the number of bytes in the first utf-8 character of *utf8.
|
40
|
+
*/
|
41
|
+
static size_t
|
42
|
+
utf8_char_bytes(const char *utf8)
|
43
|
+
{
|
44
|
+
size_t i;
|
45
|
+
const unsigned char *u;
|
46
|
+
|
47
|
+
if (utf8[0] < 0x80) {
|
48
|
+
return 1;
|
49
|
+
}
|
50
|
+
|
51
|
+
u = (const unsigned char *) utf8;
|
52
|
+
|
53
|
+
for (i = 1; i < 6; i++) {
|
54
|
+
if ((u[i] & 0xc0) != 0x80) {
|
55
|
+
return i;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
return -1;
|
60
|
+
}
|
61
|
+
|
62
|
+
/*
|
63
|
+
* Ensures *r is long enough to hold len chars (using realloc).
|
64
|
+
*
|
65
|
+
* Arguments:
|
66
|
+
* - char **r: Pointer to the string (which need not be \0-terminated). Will
|
67
|
+
* be replaced by the new string.
|
68
|
+
* - size_t *r_len: Pointer to the length of the string. Will be replaced by
|
69
|
+
* the new length, which will be >= len.
|
70
|
+
* - size_t len: Minimum length of the new string.
|
71
|
+
*
|
72
|
+
* Returns:
|
73
|
+
* - 0 on success, whether or not *r changed length.
|
74
|
+
* - ENOMEM if reallocation failed. *r and *r_len will point to their old
|
75
|
+
* values, which will remain unchanged.
|
76
|
+
*/
|
77
|
+
static int
|
78
|
+
ensure_string_length(
|
79
|
+
char **r,
|
80
|
+
size_t *r_len,
|
81
|
+
size_t len,
|
82
|
+
HtmlNamespacingAllocationStrategy allocation_strategy)
|
83
|
+
{
|
84
|
+
char *new_r;
|
85
|
+
size_t new_r_len;
|
86
|
+
|
87
|
+
new_r_len = *r_len;
|
88
|
+
if (new_r_len >= len) {
|
89
|
+
/* No need to realloc */
|
90
|
+
return 0;
|
91
|
+
}
|
92
|
+
|
93
|
+
while (new_r_len < len) {
|
94
|
+
new_r_len <<= 1;
|
95
|
+
}
|
96
|
+
|
97
|
+
new_r = allocation_strategy.realloc(*r, sizeof(char) * new_r_len);
|
98
|
+
if (!new_r) {
|
99
|
+
return ENOMEM;
|
100
|
+
}
|
101
|
+
|
102
|
+
*r = new_r;
|
103
|
+
*r_len = new_r_len;
|
104
|
+
return 0;
|
105
|
+
}
|
106
|
+
|
107
|
+
/*
|
108
|
+
* Tries to copy a single utf8 character to dest, possibly reallocating.
|
109
|
+
*
|
110
|
+
* Arguments:
|
111
|
+
* - dest: Beginning of destination string. May be reallocated during copy.
|
112
|
+
* - dest_len: Amount of memory allocated to dest. May be increased during
|
113
|
+
* copy.
|
114
|
+
* - dest_p: Pointer to end of destination string (potentially 1 past the
|
115
|
+
* allocated length of dest).
|
116
|
+
* - src_p: Source string. Will be advanced.
|
117
|
+
*
|
118
|
+
* Returns:
|
119
|
+
* - 0 on success. dest may be changed; dest_p will be incremented; src_p
|
120
|
+
* will be incremented.
|
121
|
+
* - ENOMEM if reallocation failed. dest, dest_p, and src_p will remain
|
122
|
+
* unchanged.
|
123
|
+
*/
|
124
|
+
static int
|
125
|
+
append_next_utf8_char(
|
126
|
+
char **dest,
|
127
|
+
size_t *dest_len,
|
128
|
+
char **dest_p,
|
129
|
+
const char **src_p,
|
130
|
+
HtmlNamespacingAllocationStrategy allocation_strategy)
|
131
|
+
{
|
132
|
+
size_t c_num_utf8_bytes;
|
133
|
+
size_t dest_p_offset;
|
134
|
+
size_t new_dest_len;
|
135
|
+
char *new_dest;
|
136
|
+
int rv;
|
137
|
+
|
138
|
+
c_num_utf8_bytes = utf8_char_bytes(*src_p);
|
139
|
+
dest_p_offset = *dest_p - *dest;
|
140
|
+
new_dest = *dest;
|
141
|
+
new_dest_len = dest_p_offset + c_num_utf8_bytes;
|
142
|
+
|
143
|
+
rv = ensure_string_length(
|
144
|
+
&new_dest, dest_len, new_dest_len, allocation_strategy);
|
145
|
+
if (rv == ENOMEM) {
|
146
|
+
return ENOMEM;
|
147
|
+
}
|
148
|
+
if (new_dest != *dest) {
|
149
|
+
*dest = new_dest;
|
150
|
+
*dest_p = new_dest + dest_p_offset;
|
151
|
+
}
|
152
|
+
|
153
|
+
strncpy(*dest_p, *src_p, c_num_utf8_bytes);
|
154
|
+
*dest_p += c_num_utf8_bytes;
|
155
|
+
*src_p += c_num_utf8_bytes;
|
156
|
+
|
157
|
+
return 0;
|
158
|
+
}
|
159
|
+
|
160
|
+
static int
|
161
|
+
append_string(
|
162
|
+
char **dest,
|
163
|
+
size_t *dest_len,
|
164
|
+
char **dest_p,
|
165
|
+
const char *s,
|
166
|
+
HtmlNamespacingAllocationStrategy allocation_strategy)
|
167
|
+
{
|
168
|
+
int rv = 0;
|
169
|
+
|
170
|
+
while (*s && rv == 0) {
|
171
|
+
rv = append_next_utf8_char(
|
172
|
+
dest, dest_len, dest_p, &s, allocation_strategy);
|
173
|
+
}
|
174
|
+
|
175
|
+
return rv;
|
176
|
+
};
|
177
|
+
|
178
|
+
static int
|
179
|
+
append_end_of_string(
|
180
|
+
char **dest,
|
181
|
+
size_t *dest_len,
|
182
|
+
char **dest_p,
|
183
|
+
HtmlNamespacingAllocationStrategy allocation_strategy)
|
184
|
+
{
|
185
|
+
int rv;
|
186
|
+
const char *end = "\0";
|
187
|
+
|
188
|
+
rv = append_next_utf8_char(
|
189
|
+
dest, dest_len, dest_p, &end, allocation_strategy);
|
190
|
+
|
191
|
+
if (rv == 0) {
|
192
|
+
/* We don't add the length of '\0' */
|
193
|
+
*dest_len -= 1;
|
194
|
+
*dest_p -= 1;
|
195
|
+
}
|
196
|
+
|
197
|
+
return rv;
|
198
|
+
}
|
199
|
+
|
200
|
+
enum {
|
201
|
+
PARSE_NORMAL,
|
202
|
+
PARSE_OPEN_TAG,
|
203
|
+
PARSE_OPEN_TAG_NAME,
|
204
|
+
PARSE_OPEN_TAG_ATTRIBUTE_NAME,
|
205
|
+
PARSE_OPEN_TAG_ATTRIBUTE_VALUE,
|
206
|
+
PARSE_OPEN_TAG_ATTRIBUTE_EQUALS,
|
207
|
+
PARSE_CLOSE_TAG,
|
208
|
+
PARSE_EMPTY_TAG, /* detected while we're inside PARSE_OPEN_TAG */
|
209
|
+
PARSE_COMMENT,
|
210
|
+
PARSE_XML_DECL,
|
211
|
+
PARSE_DOCTYPE,
|
212
|
+
PARSE_CDATA
|
213
|
+
};
|
214
|
+
|
215
|
+
static int
|
216
|
+
char_is_whitespace(char c)
|
217
|
+
{
|
218
|
+
return c == 0x20 || c == 0x09 || c == 0x0d || c == 0x0a;
|
219
|
+
}
|
220
|
+
|
221
|
+
static int
|
222
|
+
should_ignore_tag(const char *tag_name, size_t tag_len)
|
223
|
+
{
|
224
|
+
int i = 0;
|
225
|
+
const char *test_ignore;
|
226
|
+
|
227
|
+
for (i = 0; test_ignore = IGNORE_TAGS[i]; i++) {
|
228
|
+
if (0 == strncmp(test_ignore, tag_name, tag_len)
|
229
|
+
&& strlen(test_ignore) == tag_len)
|
230
|
+
{
|
231
|
+
return 1;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
return 0;
|
236
|
+
}
|
237
|
+
|
238
|
+
/*
|
239
|
+
* Adjusts the given HTML so that top-level elements get an added "class" of
|
240
|
+
* namespace.
|
241
|
+
*
|
242
|
+
* For instance, given
|
243
|
+
* "<div><span>Hello</span></div><p class="below">Goodbye</p>" and a
|
244
|
+
* namespace of "foo", returns
|
245
|
+
* "<div class="foo"><span>Hello</span></div><p class="below foo">Goodbye</p>".
|
246
|
+
*
|
247
|
+
* Arguments:
|
248
|
+
* - const char *html: Original HTML string. Must be valid HTML, encoded as
|
249
|
+
* utf-8.
|
250
|
+
* - int html_len: Length of HTML string.
|
251
|
+
* - const char *namespace: Namespace to add. Must be of the form
|
252
|
+
* "[a-zA-Z][-_a-zA-Z0-9]*".
|
253
|
+
* - char **ret: Pointer to return value, which will be newly-allocated. Will
|
254
|
+
* be overwritten only on success.
|
255
|
+
* - int *ret_len: Pointer to return value's length. Will be overwritten only
|
256
|
+
* on success.
|
257
|
+
*
|
258
|
+
* Returns:
|
259
|
+
* - 0 on success. *ret will be overwritten to point to the newly-allocated
|
260
|
+
* string.
|
261
|
+
* - ENOMEM if *ret could not be allocated.
|
262
|
+
* - EINVAL if html is not valid XML.
|
263
|
+
*/
|
264
|
+
int
|
265
|
+
add_namespace_to_html_with_length_and_allocation_strategy(
|
266
|
+
const char *html,
|
267
|
+
size_t html_len,
|
268
|
+
const char *ns,
|
269
|
+
char **ret,
|
270
|
+
size_t *ret_len,
|
271
|
+
HtmlNamespacingAllocationStrategy allocation_strategy)
|
272
|
+
{
|
273
|
+
|
274
|
+
#define APPEND_NEXT_CHAR() \
|
275
|
+
if (append_next_utf8_char(&r, &r_len, &r_p, &html, allocation_strategy) != 0) goto error/*;*/
|
276
|
+
#define APPEND_STRING(s) \
|
277
|
+
if (append_string(&r, &r_len, &r_p, s, allocation_strategy) != 0) goto error/*;*/
|
278
|
+
#define APPEND_END_OF_STRING() \
|
279
|
+
if (append_end_of_string(&r, &r_len, &r_p, allocation_strategy) != 0) goto error/*;*/
|
280
|
+
|
281
|
+
unsigned int state;
|
282
|
+
char *r; /* Start of retval */
|
283
|
+
char *r_p; /* Pointer in retval */
|
284
|
+
size_t r_len; /* Length of retval */
|
285
|
+
const char *html_start;
|
286
|
+
const char *open_tag_name;
|
287
|
+
size_t open_tag_name_len;
|
288
|
+
char c;
|
289
|
+
size_t num_chars_remaining;
|
290
|
+
int num_tags_open;
|
291
|
+
int open_tag_attribute_is_class_attribute;
|
292
|
+
int open_tag_had_class_attribute;
|
293
|
+
const char *open_tag_attribute_value;
|
294
|
+
|
295
|
+
r_len = round_to_power_of_two(html_len);
|
296
|
+
r = allocation_strategy.malloc(sizeof(char) * r_len);
|
297
|
+
if (!r) {
|
298
|
+
return ENOMEM;
|
299
|
+
}
|
300
|
+
|
301
|
+
html_start = html;
|
302
|
+
state = PARSE_NORMAL;
|
303
|
+
r_p = r;
|
304
|
+
num_tags_open = 0;
|
305
|
+
open_tag_attribute_is_class_attribute = 0;
|
306
|
+
open_tag_had_class_attribute = 0;
|
307
|
+
open_tag_attribute_value = NULL;
|
308
|
+
|
309
|
+
while (1) {
|
310
|
+
num_chars_remaining = html_len - (html - html_start);
|
311
|
+
if (num_chars_remaining <= 0) break;
|
312
|
+
|
313
|
+
c = *html;
|
314
|
+
switch (state) {
|
315
|
+
case PARSE_NORMAL:
|
316
|
+
if (c == '<') {
|
317
|
+
APPEND_NEXT_CHAR();
|
318
|
+
if (num_chars_remaining >= 9
|
319
|
+
&& 0 == strncmp("![CDATA[", html, 8)) {
|
320
|
+
state = PARSE_CDATA;
|
321
|
+
} else if (num_chars_remaining >= 2 && html[0] == '/') {
|
322
|
+
state = PARSE_CLOSE_TAG;
|
323
|
+
} else if (num_chars_remaining >= 4
|
324
|
+
&& 0 == strncmp("!--", html, 3)) {
|
325
|
+
state = PARSE_COMMENT;
|
326
|
+
} else if (num_chars_remaining >= 9
|
327
|
+
&& 0 == strncmp("!DOCTYPE", html, 8)) {
|
328
|
+
state = PARSE_DOCTYPE;
|
329
|
+
} else if (num_chars_remaining >= 5
|
330
|
+
&& 0 == strncmp("?xml", html, 4)) {
|
331
|
+
state = PARSE_XML_DECL;
|
332
|
+
} else {
|
333
|
+
open_tag_name = html;
|
334
|
+
state = PARSE_OPEN_TAG_NAME;
|
335
|
+
}
|
336
|
+
} else {
|
337
|
+
APPEND_NEXT_CHAR();
|
338
|
+
}
|
339
|
+
break;
|
340
|
+
case PARSE_OPEN_TAG_NAME:
|
341
|
+
if (char_is_whitespace(c) || c == '>' || c == '/') {
|
342
|
+
open_tag_name_len = html - open_tag_name;
|
343
|
+
state = PARSE_OPEN_TAG;
|
344
|
+
}
|
345
|
+
if (c != '>' && c != '/') {
|
346
|
+
APPEND_NEXT_CHAR();
|
347
|
+
}
|
348
|
+
break;
|
349
|
+
case PARSE_OPEN_TAG:
|
350
|
+
if (c == '/' || c == '>') {
|
351
|
+
if (num_tags_open == 0 && !open_tag_had_class_attribute
|
352
|
+
&& !should_ignore_tag(open_tag_name, open_tag_name_len)) {
|
353
|
+
APPEND_STRING(" class=\"");
|
354
|
+
APPEND_STRING(ns);
|
355
|
+
APPEND_STRING("\"");
|
356
|
+
}
|
357
|
+
|
358
|
+
open_tag_had_class_attribute = 0;
|
359
|
+
open_tag_attribute_value = NULL;
|
360
|
+
|
361
|
+
if (c == '/') {
|
362
|
+
APPEND_STRING(" ");
|
363
|
+
state = PARSE_EMPTY_TAG;
|
364
|
+
} else {
|
365
|
+
num_tags_open++;
|
366
|
+
state = PARSE_NORMAL;
|
367
|
+
}
|
368
|
+
} else if (!char_is_whitespace(c)) {
|
369
|
+
if (num_chars_remaining >= 5
|
370
|
+
&& 0 == strncmp(html, "class", 5)) {
|
371
|
+
open_tag_attribute_is_class_attribute = 1;
|
372
|
+
open_tag_had_class_attribute = 1;
|
373
|
+
} else {
|
374
|
+
open_tag_attribute_is_class_attribute = 0;
|
375
|
+
}
|
376
|
+
state = PARSE_OPEN_TAG_ATTRIBUTE_NAME;
|
377
|
+
}
|
378
|
+
APPEND_NEXT_CHAR();
|
379
|
+
break;
|
380
|
+
case PARSE_OPEN_TAG_ATTRIBUTE_NAME:
|
381
|
+
if (char_is_whitespace(c) || c == '=') {
|
382
|
+
state = PARSE_OPEN_TAG_ATTRIBUTE_EQUALS;
|
383
|
+
}
|
384
|
+
APPEND_NEXT_CHAR();
|
385
|
+
break;
|
386
|
+
case PARSE_OPEN_TAG_ATTRIBUTE_EQUALS:
|
387
|
+
if (c == '\'' || c == '"') {
|
388
|
+
open_tag_attribute_value = html;
|
389
|
+
state = PARSE_OPEN_TAG_ATTRIBUTE_VALUE;
|
390
|
+
}
|
391
|
+
APPEND_NEXT_CHAR();
|
392
|
+
break;
|
393
|
+
case PARSE_OPEN_TAG_ATTRIBUTE_VALUE:
|
394
|
+
/* open_tag_attribute_value is either ' or " */
|
395
|
+
if (c == *open_tag_attribute_value) {
|
396
|
+
if (open_tag_attribute_is_class_attribute
|
397
|
+
&& num_tags_open == 0) {
|
398
|
+
APPEND_STRING(" ");
|
399
|
+
APPEND_STRING(ns);
|
400
|
+
}
|
401
|
+
open_tag_attribute_is_class_attribute = 0;
|
402
|
+
state = PARSE_OPEN_TAG;
|
403
|
+
}
|
404
|
+
APPEND_NEXT_CHAR();
|
405
|
+
break;
|
406
|
+
case PARSE_CLOSE_TAG:
|
407
|
+
if (c == '>') {
|
408
|
+
num_tags_open--;
|
409
|
+
open_tag_attribute_value = NULL;
|
410
|
+
state = PARSE_NORMAL;
|
411
|
+
}
|
412
|
+
APPEND_NEXT_CHAR();
|
413
|
+
break;
|
414
|
+
case PARSE_EMPTY_TAG:
|
415
|
+
case PARSE_XML_DECL:
|
416
|
+
case PARSE_DOCTYPE:
|
417
|
+
if (c == '>') {
|
418
|
+
state = PARSE_NORMAL;
|
419
|
+
}
|
420
|
+
APPEND_NEXT_CHAR();
|
421
|
+
break;
|
422
|
+
case PARSE_COMMENT:
|
423
|
+
APPEND_NEXT_CHAR();
|
424
|
+
if (c == '-' && num_chars_remaining >= 3
|
425
|
+
&& 0 == strncmp("->", html, 2)) {
|
426
|
+
APPEND_NEXT_CHAR();
|
427
|
+
APPEND_NEXT_CHAR();
|
428
|
+
state = PARSE_NORMAL;
|
429
|
+
}
|
430
|
+
break;
|
431
|
+
case PARSE_CDATA:
|
432
|
+
APPEND_NEXT_CHAR();
|
433
|
+
if (c == ']' && num_chars_remaining >= 3
|
434
|
+
&& 0 == strncmp("]>", html, 2)) {
|
435
|
+
APPEND_NEXT_CHAR();
|
436
|
+
APPEND_NEXT_CHAR();
|
437
|
+
state = PARSE_NORMAL;
|
438
|
+
}
|
439
|
+
break;
|
440
|
+
default:
|
441
|
+
assert(0);
|
442
|
+
}
|
443
|
+
}
|
444
|
+
|
445
|
+
APPEND_END_OF_STRING();
|
446
|
+
|
447
|
+
if (state != PARSE_NORMAL
|
448
|
+
|| (r_p - r) < html_len
|
449
|
+
|| num_chars_remaining != 0
|
450
|
+
|| num_tags_open != 0
|
451
|
+
|| open_tag_attribute_is_class_attribute
|
452
|
+
|| open_tag_attribute_value)
|
453
|
+
{
|
454
|
+
allocation_strategy.free (r);
|
455
|
+
*ret = NULL;
|
456
|
+
*ret_len = -1;
|
457
|
+
return EINVAL;
|
458
|
+
}
|
459
|
+
|
460
|
+
*ret = r;
|
461
|
+
*ret_len = r_p - r;
|
462
|
+
return 0;
|
463
|
+
|
464
|
+
error:
|
465
|
+
allocation_strategy.free(r);
|
466
|
+
*ret = NULL;
|
467
|
+
*ret_len = -1;
|
468
|
+
return ENOMEM;
|
469
|
+
}
|
470
|
+
|
471
|
+
int
|
472
|
+
add_namespace_to_html_with_length(
|
473
|
+
const char *html,
|
474
|
+
size_t html_len,
|
475
|
+
const char *ns,
|
476
|
+
char **ret,
|
477
|
+
size_t *ret_len)
|
478
|
+
{
|
479
|
+
HtmlNamespacingAllocationStrategy allocation_strategy;
|
480
|
+
allocation_strategy.malloc = malloc;
|
481
|
+
allocation_strategy.free = free;
|
482
|
+
allocation_strategy.realloc = realloc;
|
483
|
+
|
484
|
+
return add_namespace_to_html_with_length_and_allocation_strategy(
|
485
|
+
html, html_len, ns, ret, ret_len, allocation_strategy);
|
486
|
+
}
|
487
|
+
|
488
|
+
|
489
|
+
#if INCLUDE_MAIN
|
490
|
+
#include <stdio.h>
|
491
|
+
|
492
|
+
int
|
493
|
+
main(int argc, char **argv)
|
494
|
+
{
|
495
|
+
const char *html;
|
496
|
+
size_t html_len;
|
497
|
+
const char *ns;
|
498
|
+
char *ret;
|
499
|
+
size_t ret_len;
|
500
|
+
int rv;
|
501
|
+
HtmlNamespacingAllocationStrategy allocation_strategy;
|
502
|
+
|
503
|
+
if (argc != 3) {
|
504
|
+
printf("usage: %s HTML_STRING NAMESPACE", argv[0]);
|
505
|
+
return 1;
|
506
|
+
}
|
507
|
+
|
508
|
+
html = argv[1];
|
509
|
+
html_len = strlen(html);
|
510
|
+
ns = argv[2];
|
511
|
+
|
512
|
+
printf("html: %s\n", html);
|
513
|
+
printf("html_len: %d\n", html_len);
|
514
|
+
printf("ns: %s\n", ns);
|
515
|
+
|
516
|
+
allocation_strategy.malloc = malloc;
|
517
|
+
allocation_strategy.free = free;
|
518
|
+
allocation_strategy.realloc = realloc;
|
519
|
+
|
520
|
+
rv = add_namespace_to_html_with_length_and_allocation_strategy(html, html_len, ns, &ret, &ret_len, allocation_strategy);
|
521
|
+
|
522
|
+
if (ret) {
|
523
|
+
printf("ret: %s\n", ret);
|
524
|
+
printf("ret_len: %d\n", ret_len);
|
525
|
+
} else {
|
526
|
+
printf("Failed!\n");
|
527
|
+
}
|
528
|
+
|
529
|
+
return rv;
|
530
|
+
}
|
531
|
+
#endif /* INCLUDE_MAIN */
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#ifndef HTML_NAMESPACING_H
|
2
|
+
#define HTML_NAMESPACING_H
|
3
|
+
|
4
|
+
#ifdef __cplusplus
|
5
|
+
extern "C" {
|
6
|
+
#endif
|
7
|
+
|
8
|
+
typedef struct _HtmlNamespacingAllocationStrategy {
|
9
|
+
void *(*malloc)(size_t size);
|
10
|
+
void (*free)(void *ptr);
|
11
|
+
void *(*realloc)(void *ptr, size_t size);
|
12
|
+
} HtmlNamespacingAllocationStrategy;
|
13
|
+
|
14
|
+
int
|
15
|
+
add_namespace_to_html_with_length(
|
16
|
+
const char *html,
|
17
|
+
size_t html_len,
|
18
|
+
const char *ns,
|
19
|
+
char **ret,
|
20
|
+
size_t *ret_len);
|
21
|
+
|
22
|
+
int
|
23
|
+
add_namespace_to_html_with_length_and_allocation_strategy(
|
24
|
+
const char *html,
|
25
|
+
size_t html_len,
|
26
|
+
const char *ns,
|
27
|
+
char **ret,
|
28
|
+
size_t *ret_len,
|
29
|
+
HtmlNamespacingAllocationStrategy allocation_strategy);
|
30
|
+
|
31
|
+
#ifdef __cplusplus
|
32
|
+
}
|
33
|
+
#endif
|
34
|
+
|
35
|
+
#endif /* HTML_NAMESPACING_H */
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#include <errno.h>
|
2
|
+
|
3
|
+
#include "ruby.h"
|
4
|
+
|
5
|
+
#include "html_namespacing.h"
|
6
|
+
|
7
|
+
#include <stdio.h>
|
8
|
+
|
9
|
+
VALUE rb_mHtmlNamespacing = Qnil;
|
10
|
+
|
11
|
+
static const HtmlNamespacingAllocationStrategy ALLOCATION_STRATEGY = {
|
12
|
+
(void *(*)(size_t)) ruby_xmalloc,
|
13
|
+
ruby_xfree,
|
14
|
+
(void *(*)(void *, size_t)) ruby_xrealloc
|
15
|
+
};
|
16
|
+
|
17
|
+
/*
|
18
|
+
* call-seq:
|
19
|
+
* HtmlNamespacing.add_namespace_to_html(html, ns) => String
|
20
|
+
*
|
21
|
+
* Returns new HTML string based on +html+ with the HTML class +ns+ to root
|
22
|
+
* elements.
|
23
|
+
*
|
24
|
+
* If +html+ is nil, returns +nil+.
|
25
|
+
*
|
26
|
+
* If +ns+ is nil, returns +html+.
|
27
|
+
*/
|
28
|
+
|
29
|
+
VALUE
|
30
|
+
html_namespacing_add_namespace_to_html(
|
31
|
+
VALUE obj,
|
32
|
+
VALUE html,
|
33
|
+
VALUE ns)
|
34
|
+
{
|
35
|
+
/*
|
36
|
+
* It's almost tempting to manually allocate the RString object to save
|
37
|
+
* ourselves the stupid extra copy. (add_namespace_to_html_with_length()
|
38
|
+
* implicitly copies the string, and here we are copying it again because
|
39
|
+
* Ruby can't convert from a char* to an RString? How lame....)
|
40
|
+
*
|
41
|
+
* But for now, let's just do the extra copy (implicit in rb_str_new2) and
|
42
|
+
* be done with it.
|
43
|
+
*/
|
44
|
+
|
45
|
+
const char *html_ptr;
|
46
|
+
size_t html_len;
|
47
|
+
const char *ns_ptr;
|
48
|
+
char *ret_ptr;
|
49
|
+
size_t ret_len;
|
50
|
+
int rv;
|
51
|
+
VALUE ret;
|
52
|
+
|
53
|
+
if (TYPE(html) == T_NIL) {
|
54
|
+
return Qnil;
|
55
|
+
}
|
56
|
+
Check_Type(html, T_STRING);
|
57
|
+
|
58
|
+
if (TYPE(ns) == T_NIL) {
|
59
|
+
return html;
|
60
|
+
}
|
61
|
+
|
62
|
+
Check_Type(ns, T_STRING);
|
63
|
+
|
64
|
+
html_ptr = RSTRING(html)->ptr;
|
65
|
+
html_len = RSTRING(html)->len;
|
66
|
+
|
67
|
+
ns_ptr = RSTRING(ns)->ptr;
|
68
|
+
|
69
|
+
rv = add_namespace_to_html_with_length_and_allocation_strategy(
|
70
|
+
html_ptr,
|
71
|
+
html_len,
|
72
|
+
ns_ptr,
|
73
|
+
&ret_ptr,
|
74
|
+
&ret_len,
|
75
|
+
ALLOCATION_STRATEGY);
|
76
|
+
if (rv == EINVAL) {
|
77
|
+
rb_raise(rb_eArgError, "Badly-formed HTML");
|
78
|
+
}
|
79
|
+
if (rv != 0) {
|
80
|
+
rb_raise(rb_eRuntimeError, "Unknown error in add_namespace_to_html");
|
81
|
+
}
|
82
|
+
|
83
|
+
ret = rb_str_new(ret_ptr, ret_len);
|
84
|
+
ruby_xfree(ret_ptr);
|
85
|
+
|
86
|
+
return ret;
|
87
|
+
}
|
88
|
+
|
89
|
+
/*
|
90
|
+
* Holds functions related to HTML namespacing.
|
91
|
+
*/
|
92
|
+
void
|
93
|
+
Init_html_namespacing_ext()
|
94
|
+
{
|
95
|
+
rb_mHtmlNamespacing = rb_define_module("HtmlNamespacing");
|
96
|
+
rb_define_module_function(rb_mHtmlNamespacing, "add_namespace_to_html", html_namespacing_add_namespace_to_html, 2);
|
97
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{html_namespacing}
|
5
|
+
s.version = "0.0.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Adam Hooper"]
|
9
|
+
s.date = %q{2009-03-17}
|
10
|
+
s.description = %q{Inserts "class=" attributes within snippets of HTML so CSS and JavaScript can use automatic scopes}
|
11
|
+
s.email = %q{adam@adamhooper.com}
|
12
|
+
s.extensions = ["ext/html_namespacing/extconf.rb"]
|
13
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
14
|
+
s.files = ["README.rdoc", "Rakefile", "test/c_extension_test.rb", "test/test_helper.rb", "ext/html_namespacing/extconf.rb", "ext/html_namespacing/html_namespacing.h", "ext/html_namespacing/html_namespacing.c", "ext/html_namespacing/html_namespacing_ext.c", "lib/html_namespacing.rb", "lib/html_namespacing/plugin/rails.rb", "Manifest", "html_namespacing.gemspec"]
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.homepage = %q{http://github.com/adamh/html_namespacing}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Html_namespacing", "--main", "README.rdoc"]
|
18
|
+
s.require_paths = ["lib", "ext"]
|
19
|
+
s.rubyforge_project = %q{html_namespacing}
|
20
|
+
s.rubygems_version = %q{1.3.1}
|
21
|
+
s.summary = %q{Automatic HTML namespacing}
|
22
|
+
s.test_files = ["test/c_extension_test.rb", "test/test_helper.rb"]
|
23
|
+
|
24
|
+
if s.respond_to? :specification_version then
|
25
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
26
|
+
s.specification_version = 2
|
27
|
+
|
28
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
29
|
+
s.add_development_dependency(%q<echoe>, [">= 0"])
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
32
|
+
end
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module HtmlNamespacing
|
2
|
+
module Plugin
|
3
|
+
module Rails
|
4
|
+
def self.install(options = {})
|
5
|
+
@options = options
|
6
|
+
install_rails_2_3
|
7
|
+
end
|
8
|
+
|
9
|
+
# Called by ActionView
|
10
|
+
def self.path_to_namespace(template)
|
11
|
+
if func = @options[:template_to_namespace_callback]
|
12
|
+
func.call(template)
|
13
|
+
elsif template.path =~ /^([^\.]+)/
|
14
|
+
$1.gsub('/', '-')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.install_rails_2_3(options = {})
|
21
|
+
::ActionView::Template.class_eval do
|
22
|
+
def render_template_with_html_namespacing(*args)
|
23
|
+
s = render_template_without_html_namespacing(*args)
|
24
|
+
|
25
|
+
if format == 'html'
|
26
|
+
HtmlNamespacing::add_namespace_to_html(s, html_namespace)
|
27
|
+
else
|
28
|
+
s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method_chain :render_template, :html_namespacing
|
33
|
+
|
34
|
+
def html_namespace
|
35
|
+
HtmlNamespacing::Plugin::Rails.path_to_namespace(self)
|
36
|
+
end
|
37
|
+
memoize :html_namespace
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class CExtensionTest < Test::Unit::TestCase
|
6
|
+
private
|
7
|
+
|
8
|
+
def self.define_test(name, html, ns, expect)
|
9
|
+
define_method("test_#{name}".to_sym) do
|
10
|
+
assert_equal(expect, f(html, ns))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.define_failing_test(name, html)
|
15
|
+
define_method("test_#{name}".to_sym) do
|
16
|
+
assert_raises(ArgumentError) do
|
17
|
+
f(html, 'X')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
self.define_test('nil HTML', nil, 'X', nil)
|
23
|
+
self.define_test('nil namespace', '<div>hello</div>', nil, '<div>hello</div>')
|
24
|
+
self.define_test('nil everything', nil, nil, nil)
|
25
|
+
self.define_test('plain text', 'hello', 'X', 'hello')
|
26
|
+
self.define_test('regular tag', '<div>hello</div>', 'X', '<div class="X">hello</div>')
|
27
|
+
self.define_test('empty tag', '<div/>', 'X', '<div class="X" />')
|
28
|
+
self.define_test('nested tag', '<div><div>hello</div></div>', 'X', '<div class="X"><div>hello</div></div>')
|
29
|
+
self.define_test('two tags', '<div>hello</div><div>goodbye</div>', 'X', '<div class="X">hello</div><div class="X">goodbye</div>')
|
30
|
+
self.define_test('existing class= tag with double-quotes', '<div class="foo">bar</div>', 'baz', '<div class="foo baz">bar</div>')
|
31
|
+
self.define_test('existing class= tag with single-quotes', "<div class='foo'>bar</div>", 'baz', "<div class='foo baz'>bar</div>")
|
32
|
+
self.define_test('other attributes are ignored', '<div id="id" class="foo" style="display:none;">bar</div>', 'baz', '<div id="id" class="foo baz" style="display:none;">bar</div>')
|
33
|
+
self.define_test('works with utf-8', '<div class="𝞪">𝟂</div>', '𝞺', '<div class="𝞪 𝞺">𝟂</div>')
|
34
|
+
self.define_test('empty tag with existing class=', '<span class="foo"/>', 'bar', '<span class="foo bar" />')
|
35
|
+
self.define_test('works with newlines in tag', "<div\n\nclass\n\n=\n\n'foo'\n\n>bar</div>", 'baz', "<div\n\nclass\n\n=\n\n'foo baz'\n\n>bar</div>")
|
36
|
+
self.define_test('ignores XML prolog', '<?xml version="1.0"?><div>foo</div>', 'X', '<?xml version="1.0"?><div class="X">foo</div>')
|
37
|
+
self.define_test('ignores DOCTYPE', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><div>foo</div>', 'X', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><div class="X">foo</div>')
|
38
|
+
self.define_test('ignores CDATA', '<![CDATA[ignore <div class="foo">]] </div>]]><div>foo</div>', 'X', '<![CDATA[ignore <div class="foo">]] </div>]]><div class="X">foo</div>')
|
39
|
+
self.define_test('ignores comments', '<!-- blah <div class="foo">foo</div> - <span/>--><div>foo</div>', 'X', '<!-- blah <div class="foo">foo</div> - <span/>--><div class="X">foo</div>')
|
40
|
+
|
41
|
+
[ 'html', 'head', 'base', 'meta', 'title', 'link', 'script', 'noscript', 'style' ].each do |tag|
|
42
|
+
self.define_test("ignores <#{tag}> tags", "<#{tag}>foo</#{tag}>", "X", "<#{tag}>foo</#{tag}>")
|
43
|
+
end
|
44
|
+
|
45
|
+
self.define_failing_test('unclosed tag fails', '<div>foo')
|
46
|
+
self.define_failing_test('closing tag fails', 'foo</div>')
|
47
|
+
self.define_failing_test('wrong attr syntax fails', '<div foo=bar>foo</div>')
|
48
|
+
|
49
|
+
def f(html, ns)
|
50
|
+
HtmlNamespacing::add_namespace_to_html(html, ns)
|
51
|
+
end
|
52
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adamh-html_namespacing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Hooper
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-17 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: echoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Inserts "class=" attributes within snippets of HTML so CSS and JavaScript can use automatic scopes
|
26
|
+
email: adam@adamhooper.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions:
|
30
|
+
- ext/html_namespacing/extconf.rb
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- test/c_extension_test.rb
|
37
|
+
- test/test_helper.rb
|
38
|
+
- ext/html_namespacing/extconf.rb
|
39
|
+
- ext/html_namespacing/html_namespacing.h
|
40
|
+
- ext/html_namespacing/html_namespacing.c
|
41
|
+
- ext/html_namespacing/html_namespacing_ext.c
|
42
|
+
- lib/html_namespacing.rb
|
43
|
+
- lib/html_namespacing/plugin/rails.rb
|
44
|
+
- Manifest
|
45
|
+
- html_namespacing.gemspec
|
46
|
+
has_rdoc: true
|
47
|
+
homepage: http://github.com/adamh/html_namespacing
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options:
|
50
|
+
- --line-numbers
|
51
|
+
- --inline-source
|
52
|
+
- --title
|
53
|
+
- Html_namespacing
|
54
|
+
- --main
|
55
|
+
- README.rdoc
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
- ext
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "1.2"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project: html_namespacing
|
74
|
+
rubygems_version: 1.2.0
|
75
|
+
signing_key:
|
76
|
+
specification_version: 2
|
77
|
+
summary: Automatic HTML namespacing
|
78
|
+
test_files:
|
79
|
+
- test/c_extension_test.rb
|
80
|
+
- test/test_helper.rb
|