adamh-html_namespacing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|